Unexpected behaviour when flatMapping and emitting .finished

I'm experiencing some behaviour that I did not expect.
For example

let cancellable = [1, 2, 3]
    .publisher
    .flatMap { _ -> Empty<Void, Never> in
    print("Entered")
    return Empty(completeImmediately: true)
}.print("DEBUG").sink(receiveValue: { })

which prints

DEBUG: receive subscription: (FlatMap)
DEBUG: request unlimited
Entered
Entered
Entered
DEBUG: receive finished

but I would expect it to only print Entered once. I would expect it to finish immediately after the flatMap.

Similarly

let subject: PassthroughSubject<Void, Never> = PassthroughSubject()

let cancellable = subject
    .flatMap { _ -> Empty<Void, Never> in
    print("Entered")
    return Empty(completeImmediately: true)
}.print("DEBUG").sink(receiveValue: { })

subject.send(())

prints

DEBUG: receive subscription: (FlatMap)
DEBUG: request unlimited
Entered

It never finishes!
I would expect it to finish.

The same behaviour also happens with map { ... }.switchToLatest() which is even stranger in my opinion.

Can someone help me understand what's happening and why?

When your flatMapped publisher completes it does not complete the upstream publisher since the upstream publisher might still have values to emit.

Consider the following example:

[1, 2, 3].publisher
  .flatMap { int in 
    let url = URL(string: "https://somepage.com/page/\(int)")!
    return URLSession.shared.dataTaskPublisher(for: url)
  }

If flatMap would work like you expect it to, your pipeline might perform one network call, or it might perform three because there's no way of knowing when the first data task completes since it's run asynchronously.

That immediately demonstrates why ending the upstream publisher when the flat mapped publisher completes would be problematic.

Thanks for your explanation. I see your point.
Errors will still escape the flatMap so I have some trouble understanding why it would be different, the same logic applies. It would make sense that neither of them escape the flatMap or both of them escape.

I believe on RxSwift both .complete and .error(..) escape flatMaps

Terms of Service

Privacy Policy

Cookie Policy