Giz*_*Jon 4 mobile networking ios swift combine
对结合相当新。使用访问令牌和刷新令牌的常见场景。
你得到一个 401,你需要在重试初始调用之前处理它(调用一些服务来刷新令牌)
func dataLoader(backendURL: URL) -> AnyPublisher<Data, Error> {
let request = URLRequest(url: backendURL)
return dataPublisher(for: request)
// We get here when a request fails
.tryCatch { (error) -> AnyPublisher<(data: Data, response: URLResponse), URLError> in
guard error.errorCode == 401 else { // UPS - Unauthorized request
throw error
}
// We need to refresh token and retry -> HOW?
// And try again
// return dataPublisher(for: request)
}
.tryMap { data, response -> Data in
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
throw CustomError.invalidServerResponse
}
return data
}
.eraseToAnyPublisher()
}
Run Code Online (Sandbox Code Playgroud)
我将如何包装这个“令牌刷新服务”?
您的代码提到了“令牌”,但您没有解释那是什么。假设您有一个令牌类型:
struct Token: RawRepresentable {
var rawValue: String
}
Run Code Online (Sandbox Code Playgroud)
假设您有一个函数通过返回新令牌的发布者异步获取新令牌:
func freshToken() -> AnyPublisher<Token, Error> {
// Your code here, probably involving a URL request/response...
fatalError()
}
Run Code Online (Sandbox Code Playgroud)
假设您通过将一些 URL 与令牌组合来生成数据的 URL 请求:
func backendRequest(with url: URL, token: Token) -> URLRequest {
// Your code here, to somehow combine the url and the token into the real ...
fatalError()
}
Run Code Online (Sandbox Code Playgroud)
现在您想重试请求,每次使用新令牌,如果响应是 404。您可能应该限制尝试次数。因此,让我们编写函数来进行triesLeft计数。如果triesLeft > 1响应是 404,它将要求一个新的令牌并使用它再次调用自己(triesLeft递减)。
目标变得更加复杂,因为URLSession.DataTaskPublisher不会将 404 响应变成错误。它将其视为正常输出。
所以我们将使用嵌套的辅助函数来处理 的输出DataTaskPublisher,这样我们就不会在闭包中嵌套太多代码。名为 的辅助函数publisher(forDataTaskOutput:)根据响应决定要执行的操作。
如果响应是代码为 200 的 HTTP 响应,则它只返回数据。请注意,它必须返回一个发布者,其Failureis Error,因此它使用 aResult.Pubilsher并让 Swift 推断Failure类型。
如果响应是代码为 404 的 HTTP 响应,并且triesLeft > 1,它会调用freshToken并使用flatMap将其链接到对外部函数的另一个调用中。
否则,它会产生一个错误错误CustomError.invalidServerResponse。
func data(atBackendURL url: URL, token: Token, triesLeft: Int) -> AnyPublisher<Data, Error> {
func publisher(forDataTaskOutput output: URLSession.DataTaskPublisher.Output) -> AnyPublisher<Data, Error> {
switch (output.response as? HTTPURLResponse)?.statusCode {
case .some(200):
return Result.success(output.data).publisher.eraseToAnyPublisher()
case .some(404) where triesLeft > 1:
return freshToken()
.flatMap { data(atBackendURL: url, token: $0, triesLeft: triesLeft - 1) }
.eraseToAnyPublisher()
default:
return Fail(error: CustomError.invalidServerResponse).eraseToAnyPublisher()
}
}
let request = backendRequest(with: url, token: token)
return URLSession.shared.dataTaskPublisher(for: request)
.mapError { $0 as Error }
.flatMap(publisher(forDataTaskOutput:))
.eraseToAnyPublisher()
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1241 次 |
| 最近记录: |