Reputation: 1190
I have a list of combine publishers that each publish one optional value. From this list, i want to create a publisher, that runs the upstream publishers in the sequence they appear in the list, one after the other, and then publish the first non-nil item i can find.
My first approach was
publishers
.publisher
.flatMap(identity)
.first(where: {$0 != nil})
but this causes all publishers to run, and the fastest to win.
I created a minimal example with a solution that comes close to what i want to achieve.
import Combine
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
func delayedPublisher<Value>(_ value: Value?, delay after: Double) -> AnyPublisher<Value?, Never> {
let p = PassthroughSubject<Value?, Never>()
DispatchQueue.main.asyncAfter(deadline: .now() + after) {
p.send(value)
p.send(completion: .finished)
}
return p.eraseToAnyPublisher()
}
let delays = [1,2,3,4,5].map({ Bool.random() ? nil : $0 }).shuffled()
print("Creating publishers with values and delays (in seconds)", delays)
let myPublishers = delays
.map{ delayedPublisher($0, delay: Double($0 ?? 1))
.print("\(String(describing: $0))")
.eraseToAnyPublisher() }
let cancel = myPublishers
.publisher
.flatMap { $0 }
.collect()
.map { resultList in
resultList.first(where: { $0 != nil }) ?? nil
}
.sink { result in
print("result:", result ?? "nil")
}
This creates a bunch of publishers with different delays, that may or may not produce a value. I then collect all results, and pick the first non-nil value. The problem is
I did my research but found nothing like that, perhaps because combine is not really designed to do that sort of thing. So any pointers are appreciated.
Upvotes: 3
Views: 3537
Reputation: 172
Here's a simple extension to Publisher
extension Publisher {
func chain(with publisher: @escaping () -> AnyPublisher<Output, Failure>) -> AnyPublisher<Output, Failure> {
self.flatMap { _ in publisher() }.eraseToAnyPublisher()
}
}
Upvotes: 1
Reputation: 49590
The approach is almost like your original one, but you need to restrict flatMap
to run at most one publisher at a time with maxPublishers
parameter:
publishers
.publisher
.flatMap(maxPublishers: .max(1), { $0 })
.compactMap { $0 } // Remove all nil values and unwrap the non-nil ones.
.first()
Upvotes: 6