Nick
Nick

Reputation: 4258

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()
}

Upvotes: 0

Views: 1538

Answers (1)

New Dev
New Dev

Reputation: 49620

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.

Upvotes: 5

Related Questions