【问题标题】:Swift - Combine - Synchronous API execution inside .tryCatchSwift - Combine - .tryCatch 内的同步 API 执行
【发布时间】:2021-02-23 13:26:49
【问题描述】:

我正在尝试解决 401 错误场景,我想捕获错误,检查错误是否为 401,如果是,

  1. 刷新 oAuth
  2. 再次执行相同的 API。

目前,我正在做以下事情:

return urlSession.dataTaskPublisher(for: request)
    .tryMap(checkForAPIError)
    .tryCatch { (error) -> AnyPublisher<(data: serverData, response: URLResponse), URLError> in
        self.fetchoAuthToken()
            .tryMap { (token) in
                // Saves token
            }
            .receive(on: RunLoop.main)
            .subscribe(on: DispatchQueue.main)
            .sink { (completion) in
                 // Completion handling here
            } receiveValue: { (value) in
                print("Received \(value)")
            }
            .store(in: &self.subscription)

        return self.urlSession.dataTaskPublisher(for: request)
    }
    .tryMap(parseJson)
    .retry(3)
    .receive(on: RunLoop.main)
    .eraseToAnyPublisher()

我目前的问题是,虽然 API self.fetchoAuthToken() 仍在执行中,但块返回新请求。然后使用旧令牌执行。

我希望self.fetchoAuthToken() 同步执行,以便在它执行后可以返回并且可以使用新的令牌。

任何帮助将不胜感激。

【问题讨论】:

    标签: ios swift combine


    【解决方案1】:

    您需要链接发布者,并将链作为新发布者从tryCatch 返回。

    您通常应该避免副作用,但如果您必须 - 例如保存 OAuth 令牌,请在 .handleEvents 中执行此操作,而不是创建 sink 订阅。

    return urlSession.dataTaskPublisher(for: request)
        .tryMap(checkForAPIError)
        .tryCatch { error in
            self.fetchoAuthToken()
                .handleEvents(receiveOutput: { (token) in
                    // Saves token
                })
                .flatMap { _ in 
                    urlSession.dataTaskPublisher(for: request) 
                }
        }
        .tryMap(parseJson)
        .retry(3)
        .receive(on: RunLoop.main)
        .eraseToAnyPublisher()
    

    【讨论】:

      【解决方案2】:

      正如其他人所说,副作用(例如保存令牌)不应成为处理管道的一部分(或至少不直接)。

      建议通过添加一些辅助功能将请求逻辑一分为二:

      /// this will do the actual URLSession call
      /// fetchoAuthToken will likely call this with `withToken: false)
      func _sendRequest(_ request: URLRequest, withToken: Bool = true) -> AnyPublisher<(data: Data, response: URLResponse), Error> {
          let request = withToken ? addToken(to: request) : request
          return urlSession
              .dataTaskPublisher(for: request)
              .mapError { $0 }
              .eraseToAnyPublisher()
      }
      
      /// name explicit enough :)
      func addToken(to request: URLRequest) -> URLRequest {
          // do whathever needed (headers, cookies, etc)
      }
      
      enum MyError: Error {
          /// `fetchoAuthToken` and `checkForAPIError` should return this
          /// in case the request fails with a 401/403, or maybe some other
          /// conditions
          case notAuthenticated
      }
      

      现在,如果您将令牌获取和保存封装在fetchoAuthToken 中,并实现checkForAPIError 以在请求因该原因失败的情况下返回MyError.notAuthenticated,那么您最终可以得到一个不错的“纯”管道:

      return _sendRequest(request)
          .tryMap(checkForAPIError)
          .tryCatch { error -> AnyPublisher<(data: Data, response: URLResponse), Error> in
              if error as? MyError == .notAuthenticated {
                  return fetchToken().flatMap { _sendRequest(request) }.eraseToAnyPublisher()
              } else {
                  throw error
              }
          }
          .tryMap(parseJson)
          .retry(3, unless: \.isNotAuthenticated)
          .receive(on: RunLoop.main)
          .eraseToAnyPublisher()
      

      您可能想要解决的另一个问题是 retry 部分 - 如果 fetchoAuthToken 由于凭据无效而失败,那么您可能希望跳过重试部分,就好像凭据第一次无效一样,很可能它们也将在第二次和第三次无效。

      对于这个问题,你可以使用this 回答,并做一个retry unless

      
      extension Error {
          /// enabling sugar syntax for `retry`
          var isNotAuthenticated: Bool { self as? MyError == .notAuthenticated }
      }
      
      return _sendRequest(request)
           // rest of the pipeline omitted for readability
          .retry(3, unless: \.isNotAuthenticated)
      

      【讨论】:

        猜你喜欢
        • 2021-09-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-04-29
        • 2023-02-02
        • 2020-12-10
        • 2020-08-07
        • 2021-02-13
        相关资源
        最近更新 更多