0

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()
}
Nick
  • 3,958
  • 4
  • 32
  • 47
  • 1
    Could you change your `firstPublisher` to be of type `AnyPublisher` instead? That would significantly simplify your life and allow the use of `flatMap` to achieve your goals. – Dávid Pásztor Sep 01 '21 at 14:54
  • 1
    @matt, maybe the publisher is one that just reports success or failure for an operation, this is a legitimate scenario. Also, see the answer(s) below for solutions that **do** emit a value based on a publisher that only provides completion events. – Cristik Sep 03 '21 at 18:51

1 Answers1

5

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.

New Dev
  • 48,427
  • 12
  • 87
  • 129