什么是处理RxSwift重试和错误处理的最佳实践

时间:2019-02-19 09:35:09

标签: ios swift error-handling rx-swift

我读过一些文章说,处理RxSwift的最佳实践是仅将致命错误传递给onError,并将Result传递给onNext。

这对我来说很有意义,直到我意识到我无法处理重试了,因为它仅发生在onError上。

如何处理此问题?

另一个问题是,如何将全局和本地重试混合在一起?

一个例子就是iOS收据验证流程。

1,尝试在本地获取收据

2,如果失败,请向Apple服务器索取最新收据。

3,将收据发送到我们的后端以进行验证。

4,如果成功,则整个流程完成

5,如果失败,请检查错误代码是否可以重试,然后返回1。

,在新的1中,它将强制从Apple服务器请求新的收据。然后当它再次达到5时,整个流程将停止,因为这已经是第二次尝试了。表示只重试一次。

因此在此示例中,如果使用状态机而不使用rx,我将最终使用状态机并共享一些全局状态,例如isSecondAttempt: BoolshouldForceFetchReceipt: Bool等。

如何在rx中设计此流程?在流程中设计了这些全局共享状态。

1 个答案:

答案 0 :(得分:1)

  

我读过一些文章说,处理RxSwift的最佳实践是仅将致命错误传递给onError,并将Result传递给onNext。

我不同意这种观点。基本上是说,如果程序员犯了错误,则只能使用onError。您应该对不满意的路径使用错误或中止过程。它们就像以异步方式抛出一样。

这是您作为Rx链的算法。

enum ReceiptError: Error {
    case noReceipt
    case tooManyAttempts
}

struct Response { 
    // the server response info
}

func getReceiptResonse() -> Observable<Response> {
    return fetchReceiptLocally()
        .catchError { _ in askAppleForReceipt() }
        .flatMapLatest { data in
            sendReceiptToServer(data)
        }
        .retryWhen { error in
            error
                .scan(0) { attempts, error in
                    let max = 1
                    guard attempts < max else { throw ReceiptError.tooManyAttempts }
                    guard isRetryable(error) else { throw error }
                    return attempts + 1
                }
        }
}

以下是上面使用的支持功能:

func fetchReceiptLocally() -> Observable<Data> {
    // return the local receipt data or call `onError`
}

func sendReceiptToServer(_ data: Data) -> Observable<Response> {
    // send the receipt data or `onError` if the server failed to receive or process it correctly.
}

func isRetryable(_ error: Error) -> Bool {
    // is this error the kind that can be retried?
}

func askAppleForReceipt() -> Observable<Data> {
    return Observable.just(Bundle.main.appStoreReceiptURL)
        .map { (url) -> URL in
            guard let url = url else { throw ReceiptError.noReceipt }
            return url
        }
        .observeOn(ConcurrentDispatchQueueScheduler(qos: .userInitiated))
        .map { try Data(contentsOf: $0) }
}