Catch AnyPublisher<Void, Error> as Effect<Never, Error>

Hi all!
Just wondering if there’s a common pattern for dealing with dependencies that aren’t entirely controlled by us, or adapting ones that are.

What is the best/simplest way you have discovered for catching an AnyPublisher<Void, Error> to an Effect<Never, Failure> - is it flatMapping to Effect.none?

So far, the best way I can come up with is

publisher
  .flatMap { _ in
    Effect<Never, Error>.none
  }
  .catchToEffect()
  .map(Action.result) // where result contains a Result<Never, Error>

in the reducer (or the dependency, if I write a TCA/Effect wrapper), and handling just the .result(.failure(failure)) in the reducer.

Is this the best way to go about this, or can I simplify things? Is there a better pattern I’m just missing?

I think you're looking for .fireAndForget; see the bottom of this file! swift-composable-architecture/Effect.swift at main · pointfreeco/swift-composable-architecture · GitHub

I'm not quite looking for fireAndForget, as the dependency - GRDB - produces a Publisher that either returns Void if it succeeds, or some Error if it fails, when saving something in the database, and I still want to be able to capture when that Publisher fails. I think what I'm looking for is something more like a fireAndCatch.

Looking at the implementation for fireAndForget has helped massively though, so thank you! Something like this seems to work quite nicely:

extension Publisher {
	func fireAndCatch() -> Effect<Failure, Never> {
		flatMap { _ -> Empty<Failure, Never> in
			Empty()
		}
		.catch(Just.init)
		.eraseToEffect()
	}
}

Ahhhh i see; interesting! I could see how that would be useful. I. Like the name you came up with. :smile:

Thanks!
One of the main patterns I found I was having to use was something like this:

enum Action {
    case saveUser(User)
    case saveUserResponse(Result<Never, Failure>)
}

extension Reducer where ... { state, action, env in
    switch action {
        case let .saveUser(user):
            env.saveUser(user) // An AnyPublisher<Void, Failure>
                .flatMap { _ in Empty<Never, Failure>() }
                .catchToEffect()
                .map(Action.saveUserResponse)

        case let .saveUserResponse(.failure(failure)):
            // Do something with failure
            return .none
    }
}

You don't have to handle the .success case, because Swift knows it can't be constructed due to containing a Never, but it was still extra typing and pain both to ignore the output, turn it into an effect, and include the .failure in the resulting action. This approach appears to eliminate a lot of that extra typing :smiley: