swiftasync-awaitcombine

How to iterate over asynchronous code sequentially in Combine


I have a protocol defined as follows:

protocol Prompter {
    func promptIfNeeded() async -> Bool
}

And an array of Prompter conforming types:

var prompters: [Prompter] { [PrompterA(), PrompterB()] }

Currently, I iterate through this array, prompting each message to the user, one by one, using async await:

for prompter in prompters {
    let result = await prompter.promptIfNeeded()
    print("The result is \(result)")
}

However, I’m curious about leveraging Combine publishers instead of async await. Any guidance on this?

Note - each prompt must wait for the previous one to finish


Solution

  • If you want them to execute sequentially, you can use a Publishers.Sequence with a flatMap with a Subscribers.Demand of .max(1). Obviously, the method would need to return a Future. So, perhaps:

    protocol Prompter {
        func promptIfNeeded() -> Future<Bool, Never>
    }
    
    struct PrompterA: Prompter {
        func promptIfNeeded() -> Future<Bool, Never> {
            Future { promise in
                …
                promise(.success(result)) // where `result` is some `Bool`
            }
        }
    }
    
    struct PrompterB: Prompter { … }
    
    class Foo {
        var cancellables: Set<AnyCancellable> = []
        
        func performPrompts() {
            let prompters: [Prompter] = [PrompterA(), PrompterB()]
    
            Publishers.Sequence(sequence: prompters)
                .flatMap(maxPublishers: .max(1)) { $0.promptIfNeeded() }
                .sink { print($0) }
                .store(in: &cancellables)
        }
    }
    

    There are other variations on the theme, but hopefully this illustrates the idea: In Combine, one can use flatMap with a maxPublishers of .max(1)) for sequential behavior.