web-dev-qa-db-ja.com

tryWhenを使用してhttpエラーコードに基づいてトークンを更新する

この例は moyaとrxswiftを使用してトークンを更新する方法oauthトークン で、コンパイルするために少し変更する必要がありました。このコードは私のシナリオでは80%機能します。問題は、401エラーだけでなく、すべてのhttpエラーに対して実行されることです。必要なのは、他のすべてのhttpエラーをエラーとして渡して、他の場所で処理し、ここで飲み込まないようにすることです。

このコードでは、HttpStatus 500を取得すると、認証コードが3回実行されますが、これは明らかに私が望んでいることではありません。

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)
            })
      }
    }
  }
}
16
tskulbru

コンパイルエラー

コンパイルエラーがある行はどれですか?それはこの行になるように私には思えます:

_.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イベントが送信されます(再試行することを示します)。他のすべてのステータスコードの場合、そのObservableErrorを送信する必要があります(現在行っているように)。これは、しない再試行することを意味します。そして、エラーを伝播させたいこと。

だからこのようなもの:

_.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) }
    }
}
_
16
solidcell

catchErrorの場合、401エラーでない場合は、単にエラーをthrowする必要があります。これにより、エラーがパイプに送信されます。

1
Daniel T.