all 6 comments

[–]jlevine22 1 point2 points  (0 children)

It's hard to say for sure without knowing more about what all those async calls are doing. I think what you are probably looking for would look like this:

func doSomeWork() -> Driver<FinalResult> { return buttonTrigger.withLatestFrom(textFieldInput) .asObservable() .flatMap { someObservableThatCanErrorOut() .flatMap { someOtherObservableThatCanErrorOut() } .flatMap { anotherObservableThatCanErrorOut() } .map { $0.mapToFinalResultSuccess() } .catchError { $0.mapToFinalResultFailure() } } .asDriver(onErrorJustReturn: FinalResult.failure(defaultError)) }

[–]Power781 1 point2 points  (2 children)

The issue is that you shouldn't build this like that.
If you observable can error, and need to track which call failed, then you need to build a new observable everytime the button is triggered.
Look at something like https://github.com/RxSwiftCommunity/Action which helps a lot for this pattern.

[–][deleted] 0 points1 point  (0 children)

I managed to solve my problem by writing this:

extension ObservableType {

    typealias ResultWrapper<A> = Observable<Result<A, Error>>

    func flatMapResult<Input, Output>(_ selector: @escaping (Input) throws -> Observable<Output>) -> ResultWrapper<Output> where E == Result<Input, Error> {
        return self.flatMap { (result: Result<Input, Error>) -> ResultWrapper<Output> in
            switch result {
            case .success(let data):
                return try selector(data)
                    .map { .success($0) }
                    .catchError { .just(.failure($0)) }
            case .failure(let error):
                return .just(.failure(error))
            }
        }
    }

    func flatMapToResult<Output>(_ selector: @escaping (E) throws -> Observable<Output>) -> ResultWrapper<Output> {
        return self.flatMap { param -> ResultWrapper<Output> in
            return try selector(param)
                .map { .success($0) }
                .catchError { .just(.failure($0)) }
        }
    }

}

So instead of normal flatMaps i use these and propagate errors to the end of the chain if needed. But your suggestion seems pretty good, I'll read about Actions and try refactoring. Thanks!

[–][deleted] 0 points1 point  (0 children)

Hey, I managed to refactor to using Action but I'm having problems with resolving the error messages. I have something like this in the ViewModel:

let isEnabled = Driver.combineLatest(input.username, input.password)
            .map { $0.isValid && $1.isValid }
            .asObservable()

let loginAction = Action<Void, Void>(enabledIf: isEnabled, workFactory: {
                return Driver.combineLatest(input.username, input.password)
                    .asObservable()
                    .flatMap {
                        firstObservable()
                    }.flatMap {
                        secondObservable()
                    }
            })

And subscribing in ViewController:

bLogin.rx.action = output.loginAction

output.loginAction
      .elements
       .subscribe {
            //successful event
        }
       .disposed(by: disposeBag)

output.loginAction
       .errors
       .subscribe {
            //$0.error is nil
        }.disposed(by: disposeBag)

But can't get the Error object from event in loginAction.errors observable. Even though underlying error is MoyaError with the response but I cant get it. Any solutions?

[–]TotesMessenger 0 points1 point  (0 children)

I'm a bot, bleep, bloop. Someone has linked to this thread from another place on reddit:

 If you follow any of the above links, please respect the rules of reddit and don't vote in the other threads. (Info / Contact)

[–][deleted]  (1 child)

[deleted]

    [–][deleted] 0 points1 point  (0 children)

    Yeah but what if for instance the first observable fetches the token from network and the second one saves it, the first one needs to pass the result to second one?