Search code examples
swiftpromisefunctional-programmingcombine

Execute two promises in sequence in Swift / Combine


I have two functions with the following signatures:

import Combine

func firstCall() -> Future<Result<Void, Error>, Never> {
    return Future { promise in
        promise(.success(.success(())))
    }
}



func secondCall() -> Future<Result<Void, Error>, Never> {
    return Future { promise in
        promise(.success(.success(())))
    }
}


// Execute 1st call, then if it succeeds, execute the 2nd call

How can I execute the 2nd function only after the 1st one completes successfully?

Ideally, I'm looking to something like this:

executeInSequence([firstCall(), secondCall()])
.onResult( { finalResult in
print(finalResult)
})

Feel free to slightly adjust API signatures if that helps the call site.

More information: one of the calls is actually a converted synchronous method call with a signature of:

func secondSynchronousCall() throws {
}

    private func secondCall() -> Future<Result<Void, Error>, Never> {
        return Future { [weak self] promise in
            do {
                try self?.secondSynchronousCall()
                promise(.success(.success(())))
            } catch {
                promise(.success(.failure(error)))
            }
        }
    }

Solution

  • In Combine, Futures are just specialised publishers. Given you deal with publishers, you would do something like this:

    let cancellable = firstCall()
    .tryMap { result in
        try result.get()
    }
    .flatMap { _ in
        secondCall()
    }
    .tryMap { result in
        try result.get()
    }
    .sink { completion in
        print(completion)
    } receiveValue: { _ in
        print("reveiveValue")
    }
    

    You can write it more concisely, however, I would suggest to simplify your Output and Error types first, as already pointed out in the comments:

    For example:

    func firstCall2() -> Future<Void, Error> {
        return Future { promise in
            promise(.success(()))
            //promise(.failure(MyError()))
        }
    }
    
    func secondCall2() -> Future<Void, Error> {
        return Future { promise in
            promise(.success(()))
        }
    }
    

    and then:

    let cancellable2 = firstCall2()
    .map(secondCall2)
    .sink { completion in
        print(completion)
    } receiveValue: { _ in
        print("reveiveValue")
    }