barndog
barndog

Reputation: 7163

RAC 4 Swift Failed method

ReactiveCocoa 4 has a method on the SignalProducer class, then that waits for the producer to completed and replaces it with another producer like so:

someProducer.then(replacementProducer).

However what I want is a method that waits until the original producer errors out and then replaces it with the second producer (doing nothing on completion events).

then is implemented as (note next events are not forwarded):

public func then<U>(replacement: SignalProducer<U, Error>) -> SignalProducer<U, Error> {
    let relay = SignalProducer<U, Error> { observer, observerDisposable in
        self.startWithSignal { signal, signalDisposable in
            observerDisposable.addDisposable(signalDisposable)

            signal.observe { event in
                switch event {
                case let .Failed(error):
                    observer.sendFailed(error)
                case .Completed:
                    observer.sendCompleted()
                case .Interrupted:
                    observer.sendInterrupted()
                case .Next:
                    break
                }
            }
        }
    }
    return relay.concat(replacement)
}

What I want would look similar except on failure, instead of sending an error, it would send a completed signal. More specifically the switch statement would change to look like:

signal.observe { event in
    switch event {
        case .Failed:
            observer.sendCompleted()
        case .Completed, .Next: break
        case .Interrupted:
            observer.sendInterrupted()
        }
    }
}

My questions are as follows:

1) Is this valid? It works as I would expect, the replacement signal is only started when the first signal errors out. 2) Is there a better way to do it so that the error information doesn't get lost?

An example of where I would use this is a two-step authentication process:

self.verifyToken(token).failed(self.revalidateSession())

If the token verification succeeds, all is good and no need to revalidate. If it fails, then revalidation needs to occur.

EDIT:

I changed the signal observation block so that if the first signal completes with no error, it starts the replacement signal with startWithComplete so that completion events are forwarded down the chain without triggering any action from the signal.

That makes it easy to do this:

self.verify(token).failed(self.refreshSession(token)).then(self.fetchUserForToken(token))

where then is chained and only gets executed when either verify or refreshSession is successful.

signal.observe { event in
    switch event {
        case .Failed:
            observer.sendCompleted()
        case .Completed:
            replacement.startWithCompleted {}
        case .Next: break
        case .Interrupted:
            observer.sendInterrupted()
        }
    }
}

Upvotes: 3

Views: 487

Answers (1)

Cosyn
Cosyn

Reputation: 4987

1) Is this valid?

No. If your verifying signal producer sends a normal value and completes, your replacement signal producer does start (by the startWithCompleted call) but will produce nothing (since you are not observing anything in the startWithCompleted closure, and the inner replay signal producer will never terminate).

2) Is there a better way?

You can use flatMapError operator, this way you can also get the error information.

self.verify(token)
    .map { _ in () }
    .flatMapError { error in
        return self.refreshSession(token).map { _ in () }
    }

Note, The two map { _ in () } can be omitted if the veriy producer and refreshSession producer produce values of the same type.

Upvotes: 1

Related Questions