I have two async calls to fetch data from the server, but I want them to handle them as a single response, also want to handle errors for each response.
for example, here I have two methods m1()
, m2()
each can through the different types of errors.
We should wait to get a response of both and show an error message based on its error type. If there is no error, continue with the flow.
Which operator do we have to use? I tried with Publishers.Zip
& Publishers.Map
not able to handle errors.
enum Error1: Error {
case e1
}
enum Error2: Error {
case e2
}
func m1() -> Future<Bool, Error1> {
return Future { promise in
DispatchQueue.main.asyncAfter(deadline: .now() 2) {
promise(.failure(.e1))
}
}
}
func m2() -> Future<String, Error2> {
return Future { promise in
DispatchQueue.main.asyncAfter(deadline: .now() 3) {
promise(.success("1"))
}
}
}
Help would be greatly appreciated.!! Thank you.
CodePudding user response:
I think the the problem you are running into is related to the fact that a Publisher
can only emit one error type. So any time you try to combine m1
and m2
, each of which has a different error type, then you run into type conflict problems.
There are a couple of ways you might choose to solve this problem. I'm going to suggest one. In my solution, each of your requests (your Futures) will use an error type of Never
, but the success or failure of an individual request will be carried in a Result
. Here is the code in a Playground:
import Foundation
import Combine
enum Error1: Error {
case e1
}
enum Error2: Error {
case e2
}
func m1(shouldFail: Bool) -> Future<Result<Bool, Error1>, Never> {
return Future { promise in
DispatchQueue.main.asyncAfter(deadline: .now() 2) {
if(shouldFail) {
promise(.success(.failure(.e1)))
} else {
promise(.success(.success(true)))
}
}
}
}
func m2(shouldFail: Bool) -> Future<Result<String, Error2>, Never> {
return Future { promise in
DispatchQueue.main.asyncAfter(deadline: .now() 3) {
if(shouldFail) {
promise(.success(.failure(.e2)))
} else {
promise(.success(.success("1")))
}
}
}
}
let subscribe = m1(shouldFail: false)
.zip(m2(shouldFail: true))
.sink {
(m1:Result<Bool, Error1>, m2:Result<String, Error2>) in
switch m1 {
case .success(let boolResult) :
print("M1 succeeded with result \(boolResult)")
case .failure(_) :
print("M1 failed")
}
switch m2 {
case .success(let stringResult) :
print("M2 succeeded with result \(stringResult)")
case .failure(_) :
print("M2 failed")
}
}
(Note that I added a shouldFail
parameter to your m1 and m2 requests so that you can play with the different cases when one or the other requests fail).
Note that each of the Futures return a type of Future<SomeKindOfResult, Never>
. That means that the futures entering the combine pipeline have the same Error type (it happens to be Never
). This leads to the very odd looking construct of:
promise(.success(.failure(.e1)))
Which is a bit bizarre, but it says that the Future agrees that the request completed, and the Result
carries the success or failure of that request.
The pipeline uses zip
to wait until both requests complete. The value coming out of the zip
is a tuple (Result<Bool, Error1>, Result<String, Error2>
. This tuple accurately represents the success or failure of each request as well as carrying the appropriate value in each case.