Подтвердить что ты не робот

Использование retryWhen для обновления токенов на основе кода ошибки HTTP

Я нашел этот пример на Как обновить токен oauth с помощью moya и rxswift, который мне пришлось немного изменить, чтобы скомпилировать. Этот код работает на 80% для моего сценария. Проблема заключается в том, что он будет работать для всех ошибок HTTP, а не только 401 ошибок. Я хочу, чтобы все мои другие HTTP-ошибки передавались как ошибки, так что я могу обрабатывать их где-то там, а не проглатывать их здесь.

С помощью этого кода, если я получу HttpStatus 500, он будет запускать код аутентификации 3 раза, что явно не то, что я хочу.

Ive попытался изменить этот код, чтобы обрабатывать только дескрипторы 401, но кажется, что независимо от того, что я делаю, я не могу получить код для компиляции. Он всегда жалуется на неправильный тип возврата, "Cannot convert return expression of type Observable<Response> to return type Observable<Response>", который не имеет для меня никакого смысла.

Что я хочу: обрабатывать 401, но останавливаться на всех других ошибках

import RxSwift
import KeychainAccess
import Moya

public extension ObservableType where E == Response {

  /// Tries to refresh auth token on 401 errors and retry the request.
  /// If the refresh fails, the signal errors.
  public func retryWithAuthIfNeeded() -> Observable<E> {
    return self.retryWhen {
      (e: Observable<ErrorType>) in
      return Observable.zip(e, Observable.range(start: 1, count: 3), resultSelector: { $1 })
        .flatMap { i in
          return AuthProvider.sharedInstance.request(
            .LoginFacebookUser(
              accessToken: AuthenticationManager.defaultInstance().getLoginTokenFromKeyChain(),
              useFaceBookLogin: AuthenticationManager.defaultInstance().isFacebookLogin())
            )
            .filterSuccessfulStatusCodes()
            .mapObject(Accesstoken.self)
            .catchError {
              error in
              log.debug("ReAuth error: \(error)")
              if case Error.StatusCode(let response) = error {
                if response.statusCode == 401 {
                  // Force logout after failed attempt
                  log.debug("401:, force user logout")
                  NSNotificationCenter.defaultCenter().postNotificationName(Constants.Notifications.userNotAuthenticated, object: nil, userInfo: nil)
                }
              }
              return Observable.error(error)
            }.flatMapLatest({
              token -> Observable<Accesstoken> in
              AuthenticationManager.defaultInstance().storeServiceTokenInKeychain(token)
              return Observable.just(token)
            })
      }
    }
  }
}
4b9b3361

Ответ 1

Ошибка компиляции

В какой строке есть ошибка компиляции? Мне кажется, что это будет строка:

.catchError {
    error in
    //...
    return Observable.error(error)  // is this the line causing the compilation error?
}

Если это так, вероятно, потому, что catchError ожидает, что блок вернет Observable<Response>, с которым он может продолжить в случае ошибки, а не Observable<ErrorType>.

В любом случае, это помогает комментировать ваш код с большим количеством типов, чтобы вы могли точно определить такие проблемы, а также помочь компилятору Swift, который часто не может самостоятельно разобраться с этими вещами. Так что-то вроде этого помогло бы вам:

.catchError {
    error -> Observable<Response> in
    //...
    return Observable.error(error)  // Swift should have a more accurate and helpful error message here now
}

Обратите внимание, что я только показываю вам, что такое ошибка, и как получить Xcode, чтобы дать вам лучшие сообщения об ошибках. То, что вы пытаетесь вернуть, неверно.

Повторить только 401

Я не уверен, почему вы ожидаете, что этот код будет обрабатывать 401 по-другому (кроме публикации в центре уведомлений и регистрации). Как бы то ни было, вы уловили ошибку, но вы всегда возвращаете Observable с событием Error в конце (return Observable.error(error)), поэтому он никогда не будет повторять попытку.

Чтобы выполнить попытку 401, вы должны вернуть Observable из блока retryWhen, который отправит событие Next (означающее, что вы хотите повторить попытку). Для всех остальных кодов состояния Observable должен отправить Error (как вы сейчас делаете), что означает, что вы не хотите повторять попытку, и что вы хотите, чтобы ошибка распространялась.

Так что-то вроде этого:

.retryWhen { errorObservable -> Observable<ErrorType> in
    log.debug("ReAuth error: \(error)")
    if case Error.StatusCode(let response) = error where response.statusCode == 401 {
        log.debug("401:, force user logout")
        NSNotificationCenter.defaultCenter().postNotificationName(Constants.Notifications.userNotAuthenticated, object: nil, userInfo: nil)
        // If `401`, then return the `Observable<ErrorType>` which was given to us
        // It will emit a `.Next<ErrorType>`
        // Since it is a `.Next` event, `retryWhen` will retry.
        return errorObservable
    }
    else {
        // If not `401`, then `flatMap` the `Observable<ErrorType>` which
        // is about to emit a `.Next<ErrorType>` into
        // an `Observable<ErrorType>` which will instead emit a `.Error<ErrorType>`.
        // Since it is an `.Error` event, `retryWhen` will *not* retry.
        // Instead, it will propagate the error.
        return errorObservable.flatMap { Observable.error($0) }
    }
}

Ответ 2

Когда вы catchError, если это не ошибка 401, вам просто нужно throw ошибка. Это пошлет ошибку по трубе.