首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何将异步代码包含到请求链中?

如何将异步代码包含到请求链中?
EN

Stack Overflow用户
提问于 2020-03-15 12:30:38
回答 1查看 146关注 0票数 0

我尝试使用Siesta装饰器来启用当登录用户获得401时自动刷新我的authToken的流。对于身份验证,我使用Firebase。

在Siesta文档中,有一个关于如何链接Siesta请求的直接示例,但我找不到如何让异步Firebase getIDTokenForcingRefresh:完成:在这里工作的方法。问题是,Siesta总是期望返回一个请求或一个RequestChainAction,这在Firebase auth令牌刷新api中是不可能的。

据我所知,请求链接主要是针对只使用Siesta的用例进行的。但是,是否有一种方法可以使用异步第三方API(如FirebaseAuth ),而这些API并不完全适合图片中的内容?

以下是代码:

代码语言:javascript
复制
init() {
    configure("**") {
        $0.headers["jwt"] = self.authToken
        
        $0.decorateRequests {
          self.refreshTokenOnAuthFailure(request: $1)
     }  
  }

func refreshTokenOnAuthFailure(request: Request) -> Request {
  return request.chained {
    guard case .failure(let error) = $0.response,  // Did request fail…
      error.httpStatusCode == 401 else {           // …because of expired token?
        return .useThisResponse                    // If not, use the response we got.
    }

    return .passTo(
      self.createAuthToken().chained {             // If so, first request a new token, then:
        if case .failure = $0.response {           // If token request failed…
          return .useThisResponse                  // …report that error.
        } else {
          return .passTo(request.repeated())       // We have a new token! Repeat the original request.
        }
      }
    )
  }
}

//What to do here? This should actually return a Siesta request
func createAuthToken() -> Void {
  let currentUser = Auth.auth().currentUser
  currentUser?.getIDTokenForcingRefresh(true) { idToken, error in
    if let error = error {
      // Error
      return;
    }
    self.authToken = idToken
    self.invalidateConfiguration()
  }
}

编辑:

基于禤浩焯建议的答案,我尝试了下面的解决方案。它仍然不像预期的那样起作用:

  • 我使用请求() .post发送请求
  • 使用该解决方案,我将在回调中得到一个失败的“请求被取消”。
  • 调用createUser的回调后,将使用更新的jwt令牌发送原始请求。
  • 这个带有正确jwt令牌的新请求丢失了,因为响应->没有调用->的回调,所以在这种情况下永远不会到达onSuccess。

如何确保只在使用更新的jwt令牌发送原始请求之后才调用createUser的回调?这是我的不可行的解决方案--很高兴收到任何建议:

代码语言:javascript
复制
 // This ends up with a requestError "Request Cancelled" before the original request is triggered a second time with the refreshed jwt token.
    func createUser(user: UserModel, completion: @escaping CompletionHandler) {
    do {
        let userAsDict = try user.asDictionary()
        Api.sharedInstance.users.request(.post, json: userAsDict)
            .onSuccess {
                data in
                if let user: UserModel = data.content as? UserModel {
                    completion(user, nil)
                } else {
                    completion(nil, "Deserialization Error")
                }
        }.onFailure {
            requestError in
            completion(nil, requestError)
        }
    } catch let error {
        completion(nil, nil, "Serialization Error")
    }
}

Api班:

代码语言:javascript
复制
    class Api: Service {
    
    static let sharedInstance = Api()
    var jsonDecoder = JSONDecoder()
    var authToken: String? {
        didSet {
            // Rerun existing configuration closure using new value
            invalidateConfiguration()
            // Wipe any cached state if auth token changes
            wipeResources()
        }
    }
    
    init() {
        configureJSONDecoder(decoder: jsonDecoder)
        super.init(baseURL: Urls.baseUrl.rawValue, standardTransformers:[.text, .image])
        SiestaLog.Category.enabled = SiestaLog.Category.all
        
        configure("**") {
            $0.expirationTime = 1
            $0.headers["bearer-token"] = self.authToken
            $0.decorateRequests {
                self.refreshTokenOnAuthFailure(request: $1)
            }
        }
        
        self.configureTransformer("/users") {
            try self.jsonDecoder.decode(UserModel.self, from: $0.content)
        }
        
    }
    
    var users: Resource { return resource("/users") }
    
    func refreshTokenOnAuthFailure(request: Request) -> Request {
        return request.chained {
            guard case .failure(let error) = $0.response,  // Did request fail…
                error.httpStatusCode == 401 else {           // …because of expired token?
                    return .useThisResponse                    // If not, use the response we got.
            }
            return .passTo(
                self.refreshAuthToken(request: request).chained {          // If so, first request a new token, then:
                    if case .failure = $0.response {
                        return .useThisResponse                  // …report that error.
                    } else {
                        return .passTo(request.repeated())       // We have a new token! Repeat the original request.
                    }
                }
            )
        }
    }
    
    func refreshAuthToken(request: Request) -> Request {
        return Resource.prepareRequest(using: RefreshJwtRequest())
            .onSuccess {
                self.authToken = $0.text                  // …make future requests use it
        }
    }
}

RequestDelegate:

代码语言:javascript
复制
    class RefreshJwtRequest: RequestDelegate {

    func startUnderlyingOperation(passingResponseTo completionHandler: RequestCompletionHandler) {
        if let currentUser = Auth.auth().currentUser {
            currentUser.getIDTokenForcingRefresh(true) { idToken, error in
                if let error = error {
                    let reqError = RequestError(response: nil, content: nil, cause: error, userMessage: nil)
                    completionHandler.broadcastResponse(ResponseInfo(response: .failure(reqError)))
                    return;
                }
                let entity = Entity<Any>(content: idToken ?? "no token", contentType: "text/plain")
                completionHandler.broadcastResponse(ResponseInfo(response: .success(entity)))            }
        } else {
            let authError = RequestError(response: nil, content: nil, cause: AuthError.NOT_LOGGED_IN_ERROR, userMessage: "You are not logged in. Please login and try again.".localized())
            completionHandler.broadcastResponse(ResponseInfo(response: .failure(authError)))
        }
    }
    
    func cancelUnderlyingOperation() {}

    func repeated() -> RequestDelegate { RefreshJwtRequest() }

    private(set) var requestDescription: String = "CustomSiestaRequest"
}
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2020-03-28 21:41:50

首先,您应该重新表述问题的主旨,使其不特定于Firebase,类似于“如何使用任意异步代码而不是请求来进行请求链接?”这样对社会会有更大的帮助。然后,您可以提到Firebase auth是您的特定用例。我将相应地回答你的问题。

(编辑:回答了这个问题后,我现在看到保罗已经回答了这个问题:如何用异步任务装饰午觉请求)

午睡的RequestDelegate做你要找的东西。引用这些文档的话:“这对于接收非标准网络请求并将它们包装起来非常有用,这样它们就会看起来像是Siesta。要创建自定义请求,请将您的委托传递给Resource.prepareRequest(using:)。”

您可以使用这样的方法作为一个粗略的起点--它运行一个闭包(在您的例子中是auth调用),它要么成功而没有输出,要么返回一个错误。根据使用情况,您可以根据实际的内容对其进行调整,以填充实体。

代码语言:javascript
复制
// todo better name
class SiestaPseudoRequest: RequestDelegate {
    private let op: (@escaping (Error?) -> Void) -> Void

    init(op: @escaping (@escaping (Error?) -> Void) -> Void) {
        self.op = op
    }

    func startUnderlyingOperation(passingResponseTo completionHandler: RequestCompletionHandler) {
        op {
            if let error = $0 {
                // todo better
                let reqError = RequestError(response: nil, content: nil, cause: error, userMessage: nil)
                completionHandler.broadcastResponse(ResponseInfo(response: .failure(reqError)))
            }
            else {
                // todo you might well produce output at this point
                let ent = Entity<Any>(content: "", contentType: "text/plain")
                completionHandler.broadcastResponse(ResponseInfo(response: .success(ent)))
            }
        }
    }

    func cancelUnderlyingOperation() {}

    func repeated() -> RequestDelegate { SiestaPseudoRequest(op: op) }

    // todo better
    private(set) var requestDescription: String = "SiestaPseudoRequest"
}

我发现的一个问题是响应变压器不是针对这样的“请求”运行的--转换管道是专用于Siesta的NetworkRequest的。(这让我大吃一惊,我不确定我是否喜欢它,但西斯塔似乎总体上是充满了好的决定,所以我主要是相信这是一个很好的理由。)

这可能是值得注意的其他非请求类行为。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/60692897

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档