Swift Combine return result of first future after evaluation of second - swift

I have the following situation:
2 futures, one returns a value I am interested in, the other does some operations and returns void. The 2 are not related to each other (so the code should not be mixed), but both need to be executed in the right order in the application logic.
What I want to do is subscribe to a publisher that does the following:
future one executes and gives a value
future two executes and returns nothing
the subscriber receives the value of future one after the execution of future two.
Here is a small code example that does not compile, that shows what I would like to achieve:
import Combine
func voidFuture() -> Future<Void, Error> {
return Future<Void, Error> { promise in
promise(.success(()))
}
}
func intFuture() -> Future<Int, Error> {
return Future<Int, Error> { promise in
promise(.success(1))
}
}
func combinedFuture() -> AnyPublisher<Int, Error> {
var intValue: Int!
return intFuture().flatMap { result in
intValue = result
return voidFuture()
}.flatMap{ _ in
return CurrentValueSubject(intValue).eraseToAnyPublisher()
}.eraseToAnyPublisher()
}
var subscriptions = Set<AnyCancellable>()
combinedFuture()
.sink(receiveCompletion: { _ in }, receiveValue: { val in print(val)})
.store(in: &subscriptions)

You need to .map the Void result of the second publisher (voidFuture) back to the result of the first publisher (intFuture), which is what the .flatMap would emit:
func combinedFuture() -> AnyPublisher<Int, Error> {
intFuture().flatMap { result in
voidFuture().map { _ in result }
}
.eraseToAnyPublisher()
}

Related

Question about the conditional sentence of Observable. (RxSwift)

I tried to create a function runsample() that uses multiple observables as below.
If I meet a specific condition in the middle of the stream, I want to start from the beginning of function.
(foo1() in the example below)
In this case, how do I modify the runsample() function?
class SampleClass {
////////////////////////////////
// private
////////////////////////////////
private func foo1() -> Observable<String> {
// Do something
return .just("TEST")
}
private func foo2() -> Observable<Bool> {
// Do something
return .just(false) // or true
}
private func foo3() -> Observable<String> {
// Do something
return .just("Result")
}
////////////////////////////////
// public
////////////////////////////////
public func runSample() -> Observable<String> {
return Observable.just(())
.flatMap { [unowned self] _ in
self.foo1()
}
.flatMap { [unowned self] _ in
self.foo2()
}
// I want to retry foo1() when foo2() is false
// I want to make foo3() run only if foo2() is true.
.flatMap { [unowned self] _ in
self.foo3()
}
}
}
Based on your comment, this is what you want:
func runSample() -> Observable<String> {
struct NotValid: Error { }
return Observable.deferred {
foo1().flatMap { _ in
foo2().do(onNext: { isValid in
if !isValid { throw NotValid() }
})
}
}
.retry()
.flatMap { _ in foo3() }
}
It's a very strange requirement you have, but it's doable. I expect this is an X-Y problem though.
You really want to retry foo1()? That would imply that it failed but it obviously didn't. In any case, this will do what you want:
func runSample() -> Observable<String> {
foo1()
.flatMap { [foo2] _ in
foo2()
}
.flatMap { [foo1, foo3] isTrue in
isTrue ? foo3() : foo1()
}
}
This function will return an Observable. Every time that Observable is subscribed to, the first foo1() will be activated.
Every time the first foo1() emits a value, the value will be ignored (which is quite odd) and foo2() will be called. This will generate a new Observable which will be subscribed to.
Whenever any of the Observables generated by foo2() emit a value, if the value is true foo3() will be called, otherwise foo1() will be called. Whichever one is called, its Observable will be subscribed to.
The entire function will emit all the values that any foo1()s or foo3()s Observables emit.
Importantly for this example, you do not need to start with Observable.just(()).
Thinking about it, I'd prefer something like this:
func runSample() -> Observable<String> {
Observable.zip(foo1(), foo2())
.flatMap { $0.1 ? foo3() : .just($0.0) }
}
That way I don't have to run foo1() twice.

Create a publisher that emits a value on completion of another publisher

I have a publisher that never emits items and only completes or fails with an error (AnyPublisher<Never, Error>). I want to transform that publisher into a publisher that emits a value when the first publisher completes (AnyPublisher<Value, Error>), or passes through any error. I want to create that value after completion of the first publisher. I could do something like this, but it seems quite messy:
func demo() -> AnyPublisher<Int, Error> {
// Using Empty just for demo purposes
let firstPublisher = Empty<Never, Error>(completeImmediately: true).eraseToAnyPublisher()
var cancellable: AnyCancellable?
return Future<Int, Error> { promise in
cancellable = firstPublisher
.sink { completion in
switch completion {
case .failure(let error):
promise(.failure(error))
case .finished:
// some operation that generates value
let value:Int = 1
promise(.success(value))
}
} receiveValue: { _ in
}
}
.handleEvents(receiveCancel: {
cancellable?.cancel()
})
.eraseToAnyPublisher()
}
Can this be done a better way? Something like:
extension AnyPublisher {
func completionMap<T, P>(_: (_ completion: Subscribers.Completion<Self.Failure>) -> P) -> P where P: Publisher, T == P.Output, Self.Failure == P.Failure {
/// ???
}
}
func demo() -> AnyPublisher<Int, Error> {
// Using Empty just for demo purposes
let firstPublisher = Empty<Never, Error>(completeImmediately: true).eraseToAnyPublisher()
return firstPublisher
.completionMap { completion -> AnyPublisher<Int, Error> in
switch completion {
case .failure(let error):
return Fail(error: error).eraseToAnyPublisher()
case .finished:
// some operation that generates value
let value:Int = 1
return Just(value).setFailureType(to: Error.self).eraseToAnyPublisher()
}
}.eraseToAnyPublisher()
}
You could use .append (which returns a Publishers.Concatenate) as a way to emit a value after the first publisher completes.
let firstPublisher: AnyPublisher<Never, Error> = ...
let demo = firstPublisher
.map { _ -> Int in }
.append([1])
The above will emit 1 if firstPublisher completes successfully, or would error out.

Combine: difference between Just and first() to finish a stream

I'm new to Combine and I don't understand the behaviour in these cases:
func getPublisherWithFirst() -> AnyPublisher<Value, Error> {
// somePublisher: PassthroughSubject<Bool, Never>
return somePublisher
.compactMap { (someBool) -> Value? in
if someBool {
return Value()
}
return nil
}
.setFailureType(to: Error.self)
.first()
.timeout(.seconds(5), scheduler: DispatchQueue.main)
.eraseToAnyPublisher()
}
func getPublisherWithFlatMap() -> AnyPublisher<Value, Error> {
// somePublisher: PassthroughSubject<Bool, Never>
return somePublisher
.flatMap { (someBool) -> AnyPublisher<Value, Never> in
if someBool {
return Just(Value()).eraseToAnyPublisher()
}
return Empty(completeImmediately: false, outputType: Value.self, failureType: Never.self).eraseToAnyPublisher()
}
.setFailureType(to: Error.self)
.timeout(.seconds(5), scheduler: DispatchQueue.main)
.eraseToAnyPublisher()
}
When using getPublisherWithFlatMap, my stream does not finish (the timeout is triggered), even if a Just publisher is returned and it is supposed to finish. Why?
The "issue" is caused by you using a PassthroughSubject as your upstream. PassthroughSubject never completes unless you explicitly send a completion on it.
When using first, it doesn't matter if the upstream completes or not, since first completes as soon as its upstream emits a single value. However, in case of flatMap, flatMap executes its closure on every single value emitted by its upstream and hence it only completes when its upstream completes. It doesn't matter that you return a Just inside your flatMap, that Just will only be returned for a single value, not for the upstream as a whole.
If you want to ensure that getPublisherWithFlatMap also completes after emitting a single value, you should call first on it as well, similarly to getPublisherWithFirst.
func getPublisherWithFlatMap() -> AnyPublisher<Value, Error> {
// somePublisher: PassthroughSubject<Bool, Never>
return somePublisher
.flatMap { (someBool) -> AnyPublisher<Value, Never> in
if someBool {
return Just(Value()).eraseToAnyPublisher()
}
return Empty(completeImmediately: false, outputType: Value.self, failureType: Never.self).eraseToAnyPublisher()
}
.setFailureType(to: Error.self)
.first()
.timeout(.seconds(5), scheduler: DispatchQueue.main)
.eraseToAnyPublisher()
}

How to replicate PromiseKit-style chained async flow using Combine + Swift

I was using PromiseKit successfully in a project until Xcode 11 betas broke PK v7. In an effort to reduce external dependencies, I decided to scrap PromiseKit. The best replacement for handling chained async code seemed to be Futures using the new Combine framework.
I am struggling to replicate the simple PK syntax using Combine
ex. simple PromiseKit chained async call syntax
getAccessCodeFromSyncProvider.then{accessCode in startSync(accessCode)}.then{popToRootViewController}.catch{handleError(error)}
I understand:
A Swift standard library implementation of async/await would solve this problem (async/await does not yet exist, despite lots of chatter and involvement from Chris Latter himself)
I could replicate using Semaphores (error-prone?)
flatMap can be used to chain Futures
The async code I'd like should be able to be called on demand, since it's involved with ensuring user is logged in. I'm wrestling with two conceptual problems.
If I wrap Futures in a method, with sink to handle result, it seems that the method goes out of scope before subscriber is called by sink.
Since Futures execute only once, I worry that if I call the method multiple times I'll only get the old, stale, result from the first call. To work around this, maybe I would use a PassthroughSubject? This allows the Publisher to be called on demand.
Questions:
Do I have to retain every publisher and subscriber outside of the
calling method
How can I replicate simple chained async using the Swift standard library and then embed this in a swift instance method I can call on-demand to restart the chained async calls from the top??
//how is this done using Combine?
func startSync() {
getAccessCodeFromSyncProvider.then{accessCode in startSync(accessCode)}.catch{\\handle error here}
}
This is not a real answer to your whole question — only to the part about how to get started with Combine. I'll demonstrate how to chain two asynchronous operations using the Combine framework:
print("start")
Future<Bool,Error> { promise in
delay(3) {
promise(.success(true))
}
}
.handleEvents(receiveOutput: {_ in print("finished 1")})
.flatMap {_ in
Future<Bool,Error> { promise in
delay(3) {
promise(.success(true))
}
}
}
.handleEvents(receiveOutput: {_ in print("finished 2")})
.sink(receiveCompletion: {_ in}, receiveValue: {_ in print("done")})
.store(in:&self.storage) // storage is a persistent Set<AnyCancellable>
First of all, the answer to your question about persistence is: the final subscriber must persist, and the way to do this is using the .store method. Typically you'll have a Set<AnyCancellable> as a property, as here, and you'll just call .store as the last thing in the pipeline to put your subscriber in there.
Next, in this pipeline I'm using .handleEvents just to give myself some printout as the pipeline moves along. Those are just diagnostics and wouldn't exist in a real implementation. All the print statements are purely so we can talk about what's happening here.
So what does happen?
start
finished 1 // 3 seconds later
finished 2 // 3 seconds later
done
So you can see we've chained two asynchronous operations, each of which takes 3 seconds.
How did we do it? We started with a Future, which must call its incoming promise method with a Result as a completion handler when it finishes. After that, we used .flatMap to produce another Future and put it into operation, doing the same thing again.
So the result is not beautiful (like PromiseKit) but it is a chain of async operations.
Before Combine, we'd have probably have done this with some sort of Operation / OperationQueue dependency, which would work fine but would have even less of the direct legibility of PromiseKit.
Slightly more realistic
Having said all that, here's a slightly more realistic rewrite:
var storage = Set<AnyCancellable>()
func async1(_ promise:#escaping (Result<Bool,Error>) -> Void) {
delay(3) {
print("async1")
promise(.success(true))
}
}
func async2(_ promise:#escaping (Result<Bool,Error>) -> Void) {
delay(3) {
print("async2")
promise(.success(true))
}
}
override func viewDidLoad() {
print("start")
Future<Bool,Error> { promise in
self.async1(promise)
}
.flatMap {_ in
Future<Bool,Error> { promise in
self.async2(promise)
}
}
.sink(receiveCompletion: {_ in}, receiveValue: {_ in print("done")})
.store(in:&self.storage) // storage is a persistent Set<AnyCancellable>
}
As you can see, the idea that is our Future publishers simply have to pass on the promise callback; they don't actually have to be the ones who call them. A promise callback can thus be called anywhere, and we won't proceed until then.
You can thus readily see how to replace the artificial delay with a real asynchronous operation that somehow has hold of this promise callback and can call it when it completes. Also my promise Result types are purely artificial, but again you can see how they might be used to communicate something meaningful down the pipeline. When I say promise(.success(true)), that causes true to pop out the end of the pipeline; we are disregarding that here, but it could be instead a downright useful value of some sort, possibly even the next Future.
(Note also that we could insert .receive(on: DispatchQueue.main) at any point in the chain to ensure that what follows immediately is started on the main thread.)
Slightly neater
It also occurs to me that we could make the syntax neater, perhaps a little closer to PromiseKit's lovely simple chain, by moving our Future publishers off into constants. If you do that, though, you should probably wrap them in Deferred publishers to prevent premature evaluation. So for example:
var storage = Set<AnyCancellable>()
func async1(_ promise:#escaping (Result<Bool,Error>) -> Void) {
delay(3) {
print("async1")
promise(.success(true))
}
}
func async2(_ promise:#escaping (Result<Bool,Error>) -> Void) {
delay(3) {
print("async2")
promise(.success(true))
}
}
override func viewDidLoad() {
print("start")
let f1 = Deferred{Future<Bool,Error> { promise in
self.async1(promise)
}}
let f2 = Deferred{Future<Bool,Error> { promise in
self.async2(promise)
}}
// this is now extremely neat-looking
f1.flatMap {_ in f2 }
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: {_ in}, receiveValue: {_ in print("done")})
.store(in:&self.storage) // storage is a persistent Set<AnyCancellable>
}
matt's answer is correct, use flatMap to chain promises. I got in the habit of returning promises when using PromiseKit, and carried it over to Combine (returning Futures).
I find it makes the code easier to read. Here's matt's last example with that recommendation:
var storage = Set<AnyCancellable>()
func async1() -> Future<Bool, Error> {
Future { promise in
delay(3) {
print("async1")
promise(.success(true))
}
}
}
func async2() -> Future<Bool, Error> {
Future { promise in
delay(3) {
print("async2")
promise(.success(true))
}
}
}
override func viewDidLoad() {
print("start")
async1()
.flatMap { _ in async2() }
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: {_ in}, receiveValue: {_ in print("done")})
.store(in:&self.storage) // storage is a persistent Set<AnyCancellable>
}
Note that AnyPublisher will work as a return value as well, so you could abstract away the Future and have it return AnyPublisher<Bool, Error> instead:
func async2() -> AnyPublisher<Bool, Error> {
Future { promise in
delay(3) {
print("async2")
promise(.success(true))
}
}.eraseToAnyPubilsher()
}
Also if you want to use the PromiseKit-like syntax, here are some extensions for Publisher
I am using this to seamlessly switch from PromiseKit to Combine in a project
extension Publisher {
func then<T: Publisher>(_ closure: #escaping (Output) -> T) -> Publishers.FlatMap<T, Self>
where T.Failure == Self.Failure {
flatMap(closure)
}
func asVoid() -> Future<Void, Error> {
return Future<Void, Error> { promise in
let box = Box()
let cancellable = self.sink { completion in
if case .failure(let error) = completion {
promise(.failure(error))
} else if case .finished = completion {
box.cancellable = nil
}
} receiveValue: { value in
promise(.success(()))
}
box.cancellable = cancellable
}
}
#discardableResult
func done(_ handler: #escaping (Output) -> Void) -> Self {
let box = Box()
let cancellable = self.sink(receiveCompletion: {compl in
if case .finished = compl {
box.cancellable = nil
}
}, receiveValue: {
handler($0)
})
box.cancellable = cancellable
return self
}
#discardableResult
func `catch`(_ handler: #escaping (Failure) -> Void) -> Self {
let box = Box()
let cancellable = self.sink(receiveCompletion: { compl in
if case .failure(let failure) = compl {
handler(failure)
} else if case .finished = compl {
box.cancellable = nil
}
}, receiveValue: { _ in })
box.cancellable = cancellable
return self
}
#discardableResult
func finally(_ handler: #escaping () -> Void) -> Self {
let box = Box()
let cancellable = self.sink(receiveCompletion: { compl in
if case .finished = compl {
handler()
box.cancellable = nil
}
}, receiveValue: { _ in })
box.cancellable = cancellable
return self
}
}
fileprivate class Box {
var cancellable: AnyCancellable?
}
And here's an example of use:
func someSync() {
Future<Bool, Error> { promise in
delay(3) {
promise(.success(true))
}
}
.then { result in
Future<String, Error> { promise in
promise(.success("111"))
}
}
.done { string in
print(string)
}
.catch { err in
print(err.localizedDescription)
}
.finally {
print("Finished chain")
}
}
You can use this framework for Swift coroutines, it's also can be used with Combine - https://github.com/belozierov/SwiftCoroutine
DispatchQueue.main.startCoroutine {
let future: Future<Bool, Error>
let coFuture = future.subscribeCoFuture()
let bool = try coFuture.await()
}

Swift Combine alternative to Rx Observable.create

I have some code that is built using RxSwift, and I'm playing around with converting it to use Apple's Combine framework.
One pattern which is very common is the use of Observable.create for one-shot observables (usually network requests). Something like this:
func loadWidgets() -> Observable<[Widget]> {
return Observable.create { observer in
// start the request when someone subscribes
let loadTask = WidgetLoader.request("allWidgets", completion: { widgets in
// publish result on success
observer.onNext(widgets)
observer.onComplete()
}, error: { error in
// publish error on failure
observer.onError()
})
// allow cancellation
return Disposable {
loadTask.cancel()
}
}
}
I'm trying to map that across to Combine and I haven't been able to quite figure it out. The closest I've been able to get is using Future for something like this:
func loadWidgets() -> AnyPublisher<[Widget], Error> {
return Future<[Widget], Error> { resolve in
// start the request when someone subscribes
let loadTask = WidgetLoader.request("allWidgets", completion: { widgets in
// publish result on success
resolve(.success(widgets))
}, error: { error in
// publish error on failure
resolve(.failure(error))
})
// allow cancellation ???
}
}
As you can see, it does most of it, but there's no ability to cancel.
Secondarily, future doesn't allow multiple results.
Is there any way to do something like the Rx Observable.create pattern which allows cancellation and optionally multiple results?
I think I found a way to mimic Observable.create using a PassthroughSubject in Combine. Here is the helper I made:
struct AnyObserver<Output, Failure: Error> {
let onNext: ((Output) -> Void)
let onError: ((Failure) -> Void)
let onComplete: (() -> Void)
}
struct Disposable {
let dispose: () -> Void
}
extension AnyPublisher {
static func create(subscribe: #escaping (AnyObserver<Output, Failure>) -> Disposable) -> Self {
let subject = PassthroughSubject<Output, Failure>()
var disposable: Disposable?
return subject
.handleEvents(receiveSubscription: { subscription in
disposable = subscribe(AnyObserver(
onNext: { output in subject.send(output) },
onError: { failure in subject.send(completion: .failure(failure)) },
onComplete: { subject.send(completion: .finished) }
))
}, receiveCancel: { disposable?.dispose() })
.eraseToAnyPublisher()
}
}
And here is how it looks in usage:
func loadWidgets() -> AnyPublisher<[Widget], Error> {
AnyPublisher.create { observer in
let loadTask = WidgetLoader.request("allWidgets", completion: { widgets in
observer.onNext(widgets)
observer.onComplete()
}, error: { error in
observer.onError(error)
})
return Disposable {
loadTask.cancel()
}
}
}
From what I've learned, the support for initializing an AnyPublisher with a closure has been dropped in Xcode 11 beta 3. This would be a corresponding solution for Rx's Observable.create in this case, but for now I believe that the Future is a goto solution if you only need to propagate single value. In other cases I would go for returning a PassthroughSubject and propagating multiple values this way, but it will not allow you to start a task when the observation starts and I believe it's far from ideal compared to Observable.create.
In terms of cancellation, it does not have an isDisposed property similar to a Disposable, so it's not possible to directly check the state of it and stop your own tasks from executing. The only way that I can think of right now would be to observe for a cancel event, but it's surely not as comfortable as a Disposable.
Also, I'd assume that cancel might in fact stop tasks like network requests from URLSession based on the docs here: https://developer.apple.com/documentation/combine/cancellable
Add an isCancelled operation outside the closure and check it in the future's closure. isCancelled can be toggled with the handleEvent() operator.
var isCancelled = false
func loadWidgets() -> AnyPublisher<[Widget], Error> {
return HandleEvents<Future<Any, Error>> { resolve in
// start the request when someone subscribes
let loadTask = WidgetLoader.request("allWidgets", completion: { widgets in
// publish result on success
resolve(.success(widgets))
}, error: { error in
// publish error on failure
resolve(.failure(error))
}
if isCancelled {
loadTask.cancel()
}
).handleEvents(receiveCancel: {
isCancelled = true
})
}
}
and somewhere in the app you do this to cancel the event
loadWidgets().cancel()
Also check this article
Thanks to ccwasden for the inspiration. This replicates Observable.create semantics with a pure Combine implementation without any superfluous entities.
AnyPublisher.create() Swift 5.6 Extension
public extension AnyPublisher {
static func create<Output, Failure>(_ subscribe: #escaping (AnySubscriber<Output, Failure>) -> AnyCancellable) -> AnyPublisher<Output, Failure> {
let passthroughSubject = PassthroughSubject<Output, Failure>()
var cancellable: AnyCancellable?
return passthroughSubject
.handleEvents(receiveSubscription: { subscription in
let subscriber = AnySubscriber<Output, Failure> { subscription in
} receiveValue: { input in
passthroughSubject.send(input)
return .unlimited
} receiveCompletion: { completion in
passthroughSubject.send(completion: completion)
}
cancellable = subscribe(subscriber)
}, receiveCompletion: { completion in
}, receiveCancel: {
cancellable?.cancel()
})
.eraseToAnyPublisher()
}
}
Usage
func doSomething() -> AnyPublisher<Int, Error> {
return AnyPublisher<Int, Error>.create { subscriber in
// Imperative implementation of doing something can call subscriber as follows
_ = subscriber.receive(1)
subscriber.receive(completion: .finished)
// subscriber.receive(completion: .failure(myError))
return AnyCancellable {
// Imperative cancellation implementation
}
}
}