Gil Birman
Gil Birman

Reputation: 35920

Why does Combine's receive(on:) operator swallow errors?

The following pipeline:

enum MyError: Error {
  case oops
}
let cancel = Fail<Int, Error>(error: MyError.oops)
  .print("1>")
  .print("2>")
  .sink(receiveCompletion: { status in
    print("status>", status)
  }) { value in
    print("value>", value)
}

Outputs:

1>: receive subscription: (Empty)
2>: receive subscription: (Print)
2>: request unlimited
1>: request unlimited
1>: receive error: (oops)
2>: receive error: (oops)
status> failure(__lldb_expr_126.MyError.oops)

The problem

However, if I insert a receive(on:) operator into the previous pipeline:

enum MyError: Error {
  case oops
}
let cancel = Fail<Int, Error>(error: MyError.oops)
  .print("1>")
  .receive(on: RunLoop.main)
  .print("2>")
  .sink(receiveCompletion: { status in
    print("status>", status)
  }) { value in
    print("value>", value)
}

the output is:

1>: receive subscription: (Empty)
1>: receive error: (oops)

The receive operator seems to short-circuit the pipeline. I haven't seen it happen for other publishers, just when I use a Fail or PassthroughSubject publisher.

Is this expected behavior? If so, what's the reason for it?


Workaround

Here's an example of creating a failing publisher that works with the receive(on:) publisher:

struct FooModel: Codable {
  let title: String
}

func failPublisher() -> AnyPublisher<FooModel, Error> {
  return Just(Data(base64Encoded: "")!)
    .decode(type: FooModel.self, decoder: JSONDecoder())
    .eraseToAnyPublisher()
}

let cancel = failPublisher()
  .print("1>")
  .receive(on: RunLoop.main)
  .print("2>")
  .sink(receiveCompletion: { status in
    print("status>", status)
  }) { value in
    print("value>", value)
}

Upvotes: 0

Views: 693

Answers (1)

jjoelson
jjoelson

Reputation: 5961

It's possible you're running into the same problem discussed in this post. Apparently receive(on:) will send all messages asynchronously via the given scheduler, including subscription messages. So what's happening is the error is sent before the the subscription event has a chance to be sent asynchronously, and so there is no subscriber attached to the receive publisher when the next event comes in.

However, it looks like they are changing this as of developer beta 1 of iOS 13.3:

As of developer beta 1 of iOS 13.3 (and associated releases for other platforms), we've changed the behavior of receive(on:) plus other Scheduler operators to synchronously send their subscription downstream. Previously, they would "async" it to the provided scheduler.

Upvotes: 2

Related Questions