Code:
import Combine
func login() -> Future<Token, Error> { ... }
func updateImage() -> Future<Status, Never> { ... }
func getProfile() -> Future<Profile, Error> { ... }
I need to perform something like this (sequential actions):
login()
.catch { error in
... //handle error
}
.flatMap { token in
...//handle login results
return updateImage()
}
.catch { _ in
... //skip error
}
.flatMap {
... //handle updateImage results
return getProfile()
}
.sink(...) //handle getProfile results and errors
The problem is Combine
has misleading types inside flatMap
and catch
.
Tried to return Empty
inside catch blocks:
return Empty<String, CustomError>(completeImmediately: true)
.eraseToAnyPublisher()
But I don't understand if it stops producing errors in sink
section. And is it a correct approach for my task in general?
If you want to chain multiple of these independent Future
s, and handle errors in each step, you can follow the pattern:
future().map { result in
// handle the future's result
// this implicitly returns Void, turning it into a publisher of Void
}
.catch { error in
// handle error...
// in the case of an error,
// if you want the pipeline to continue, return Just(())
// if you want the pipeline to stop, return Empty()
}
Each of these is a publisher that either publishes one ()
, or no values at all. Then you can chain multiple of these together with flatMap
:
let cancellable = login().map { token in
// handle login result...
return ()
}
.catch { error in
// handle login error...
return Just(())
}
.flatMap { _ in
updateImage().map { status in
// handle updateImage results...
}
// no need for .catch here because updateImage doesn't fail
}
.flatMap { _ in
getProfile().map { profile in
// handle getProfile results...
}.catch { error in
// handle getProfile errors...
return Just(())
}
}.sink { completion in
// handle completion
} receiveValue: { _ in
// you will only recieve a () here
}
To help the compiler figure out the types more quickly, or even at all, you should add explicit return types and/or eraseToAnyPublisher()
where appropriate.
As Dávid Pásztor's answer said, if login
and so on are async
methods instead, this chaining is built directly into the language. You can write a "chain" in the same way as you write sequential statements.
func login() async throws -> Token { ... }
func updateImage() async -> Status { ... }
func getProfile() async throws -> Profile { ... }
func asyncChain() async {
do {
let token = try await login()
// handle login results...
} catch {
// handle login error...
}
let status = await updateImage()
// handle updateImage results...
do {
let profile = try await getProfile()
// handle getProfile results...
} catch {
// handle getProfile error...
}
}