Error Handling

Error Handling

Once an error event is generated in the sequence, the entire sequence will be terminated. RxSwift has two main error handling mechanisms:

retry-retry catch-resume retry-retry

retry allows the sequence to retry after an error:

// If the request for JSON fails, retry immediately, // If it fails after 3 retry attempts, an error will be thrown

let rxJson: Observable<JSON> = ...

rxJson
    .retry(3)
 .subscribe(onNext: { json in  print("取得 JSON 成功: \(json)")  }, onError: { error in  print("取得 JSON 失败: \(error)")  })  .disposed(by: disposeBag) 

The above code is very direct retry (3), that is, when an error occurs, the retry operation is performed, and the retry is performed at most 3 times.

retryWhen

If we need to try again after a delay when an error occurs, it can be achieved as follows:

// When the request for JSON fails, wait 5 seconds and try again,

let retryDelay: Double = 5  // 重试延时 5 秒

rxJson
    .retryWhen { (rxError: Observable<Error>) -> Observable<Int> in
 return Observable.timer(retryDelay, scheduler: MainScheduler.instance)  }  .subscribe(...)  .disposed(by: disposeBag) 

Here we need to use the retryWhen operator, which mainly describes when it should be retried, and controls the timing of the retry through the Observable returned in the closure:

.retryWhen { (rxError: Observable<Error>) -> Observable<Int> in
    ...
}

The parameter in the closure is Observable, which is the sequence of errors generated, and the return value is an Observable. When the returned Observable emits an element, the operation is retried. When it emits an error or completed event, it will not retry, and will pass this event to the subsequent observer.

If you need to add a maximum number of retries:

// If the request for JSON fails, wait 5 seconds and try again. // After 4 retries and it still fails, throw an error

let maxRetryCount = 4       // 最多重试 4 次
let retryDelay: Double = 5  // 重试延时 5 秒

rxJson
 .retryWhen { (rxError: Observable<Error>) -> Observable<Int> in  return rxError.flatMapWithIndex { (error, index) -> Observable<Int> in  guard index < maxRetryCount else {  return Observable.error(error)  }  return Observable<Int>.timer(retryDelay, scheduler: MainScheduler.instance)  }  }  .subscribe(...)  .disposed(by: disposeBag) 

What we want to achieve here is that if we retry more than 4 times, an error will be thrown. If the error is within 4 times, wait 5 seconds and try again:

...
rxError.flatMapWithIndex { (error, index) -> Observable<Int> in
    guard index < maxRetryCount else {
        return Observable.error(error)
 }  return Observable<Int>.timer(retryDelay, scheduler: MainScheduler.instance) } ... 

We use the operator flatMapWithIndex because it can provide us with the wrong index number. Then use this index number to determine whether the maximum number of retries is exceeded. If it exceeds, an error is thrown. If it is not exceeded, wait 5 seconds and try again.

catchError-recovery

catchError can replace an error with a spare element or a set of spare elements when the error occurs:

searchBar.rx.text.orEmpty
    ...
    .flatMapLatest { query -> Observable<[Repository]> in
        ...
 return searchGitHub(query)  .catchErrorJustReturn([])  }  ...  .bind(to: ...)  .disposed(by: disposeBag) 

We used catchErrorJustReturn in our initial Github search. When an error occurs, an empty array is returned, and an empty list page is displayed.

You can also use catchError. When an error occurs, replace the error event with an alternative sequence:

// Get the data from the network first, if the acquisition fails, get the data from the local cache

let rxData: Observable<Data> = ...      // 网络请求的数据
let cahcedData: Observable<Data> = ...  // 之前本地缓存的数据

rxData
 .catchError { _ in cahcedData }  .subscribe(onNext: { date in  print("获取数据成功: \(date.count)")  })  .disposed(by: disposeBag) Result

If we just want to give the user an error message, how do we do it?

The following provides a most direct solution, but there are some problems with this solution:

// When the user clicks the update button, // immediately take out the modified user information. // Then initiate a network request to perform an update operation, // Once the operation fails, it prompts the user for the reason for failure

updateUserInfoButton.rx.tap
    .withLatestFrom(rxUserInfo)
    .flatMapLatest { userInfo -> Observable<Void> in
        return update(userInfo)
 }  .observeOn(MainScheduler.instance)  .subscribe(onNext: {  print("用户信息更新成功")  }, onError: { error in  print("用户信息更新失败: \(error.localizedDescription)")  })  .disposed(by: disposeBag) 

This is very straightforward. But once the network request operation fails, the sequence will terminate. The entire subscription will be cancelled. If the user clicks the update button again, he cannot initiate a network request to update again.

In order to solve this problem, we need to choose an appropriate solution for error handling. For example, use the enumeration Result that comes with the system:

public enum Result<Success, Failure> where Failure : Error {
    case success(Success)
    case failure(Failure)
}

Then the previous code needs to be modified to:

updateUserInfoButton.rx.tap
    .withLatestFrom(rxUserInfo)
    .flatMapLatest { userInfo -> Observable<Result<Void, Error>> in
        return update(userInfo)
 .map(Result.success) // 转换成 Result  .catchError { error in Observable.just(Result.failure(error)) }  }  .observeOn(MainScheduler.instance)  .subscribe(onNext: { result in  switch result { // 处理 Result  case .success:  print("用户信息更新成功")  case .failure(let error):  print("用户信息更新失败: \(error.localizedDescription)")  }  })  .disposed(by: disposeBag) 

This way our error event is wrapped into a Result.failure (Error) element, and the entire sequence will not be terminated. Even if the network request fails, the entire subscription still exists. If the user clicks the update button again, he can initiate a network request to perform the update operation.

Guess you like

Origin www.cnblogs.com/liuxiaokun/p/12684718.html