RxSwift 在错误事件上调用一个方法或者根本不调用它

RxSwift call a method on error event or don't call it at all

我有以下问题。我有两种方法都 return Single<String>。我想在开始时调用第一个(例如用户操作),如果我收到 .success 事件,我想初始化导航。但是,当发生错误时,我想先调用另一个方法进行显示,然后显示错误或根据其结果进行导航。

具体例子:

代码如下:

import UIKit
import RxSwift
import RxCocoa

final class SignInViewModel {
    private let authService: AuthenticationService
    private let disposeBag = DisposeBag()
    
    let loadingErrorOccurred = PublishSubject<Void>()
    
    init(authService: AuthenticationService) {
        self.authService = authService
    }
    
    func signInUser(in viewController: UIViewController) {
        // I want to trigger the getTokenInteractively when the getTokenSilently() fails
        // and never trigger it if it succeeds.

        getTokenSilently()
            .asObservable()
            .take(1)
            .catchAndReturn("")
            .map { token in !token.isEmpty }
            .bind(to: wasSilentTokenRequestSuccessful)
            .disposed(by: disposeBag)
    }
    
    private func getTokenSilently() -> Single<String> {
        return Single.create { [authService] observer -> Disposable in
            authService.getTokenSilently { token, error in
                if let token = token, error != nil {
                    observer(.success(token))
                } else {
                    observer(.failure(error ?? UnknownError()))
                }
            }
            
            return Disposables.create()
        }
    }
    
    private func getTokenInteractively(viewController: UIViewController) -> Single<String> {
        return Single.create { [authService] observer -> Disposable in
            authService.getTokenInteractively(parentView: viewController) { token, error in
                if let token = token, error != nil {
                    observer(.success(token))
                } else {
                    observer(.failure(error ?? UnknownError()))
                }
            }
            
            return Disposables.create()
        }
    }
}

我正在寻找实现预期结果的正确方法。我想到了某种首先触发一个函数的运算符,只有当结果失败时才会触发下一个函数。然后流的其余部分可以保持不变。

在这种情况下需要知道的一个有用的运算符是 catchError(),它将允许您用其他一些可观察的替换错误事件。

首先,我建议您将您的两个网络调用移至它们所属的身份验证服务中。

extension AuthenticationService {
    func rx_getTokenSilently() -> Single<String> {
        Single.create { observer in
            getTokenSilently { (result, error) in
                if let result = result {
                    observer(.success(result))
                }
                else {
                    observer(.error(error ?? RxError.unknown))
                }
            }
            return Disposables.create()
        }
    }

    func rx_getTokenInteractively(parentView: UIViewController) -> Single<String> {
        Single.create { observer in
            getTokenInteractively(parentView: parentView) { (result, error) in
                if let result = result {
                    observer(.success(result))
                }
                else {
                    observer(.error(error ?? RxError.unknown))
                }
            }
            return Disposables.create()
        }
    }
}

现在您的 SignInViewModel 看起来像这样:

final class SignInViewModel {
    private let authService: AuthenticationService
    private let disposeBag = DisposeBag()
    private let _token = PublishSubject<String>()
    private let _loadingErrorOccurred = PublishSubject<Error>()

    // your view controller can subscribe to these two in order to do its navigation.
    let token: Observable<String>
    let loadingErrorOccurred: Observable<Error>

    init(authService: AuthenticationService) {
        self.authService = authService
        token = _token.asObservable()
        loadingErrorOccurred = _loadingErrorOccurred.asObservable()
    }

    func signInUser(in viewController: UIViewController) {
        let tokenResult = authService.rx_getTokenSilently() // make first request
            .catchError { [authService] _ in
                // if first request fails, make second request
                authService.rx_getTokenInteractively(parentView: viewController)
            }
            .asObservable()
            .materialize()

        tokenResult.compactMap { [=11=].element } // if the either request succeeds
        .bind(to: _token) // notify the VC of the token
        .disposed(by: disposeBag)

        tokenResult
            .compactMap { [=11=].error } // if both requests fail
            .bind(to: _loadingErrorOccurred) // notify the VC of the error
            .disposed(by: disposeBag)
    }
}