Ваш код упоминает «токен», но вы не объясняете, что это такое. Предположим, у вас есть тип токена:
struct Token: RawRepresentable {
var rawValue: String
}
И, скажем, у вас есть функция, которая асинхронно получает токен fre sh, возвращая издателя токена fre sh:
func freshToken() -> AnyPublisher<Token, Error> {
// Your code here, probably involving a URL request/response...
fatalError()
}
И скажем, вы генерируете запрос 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()
}
Теперь вы хотите повторить запрос с токеном fre sh каждый раз Если ответ 404. Вероятно, вам следует ограничить количество попыток. Итак, давайте напишем функцию для подсчета triesLeft
. Если triesLeft > 1
и ответ 404, он запросит токен fre sh и использует его для повторного вызова (с уменьшением triesLeft
).
Цель становится более сложной, поскольку URLSession.DataTaskPublisher
не превращает ответ 404 в ошибку. Он обрабатывает его как нормальный вывод.
Поэтому мы будем использовать вложенную вспомогательную функцию для обработки вывода DataTaskPublisher
, чтобы у нас не было такого большого количества кода, вложенного в замыкания. Вспомогательная функция с именем publisher(forDataTaskOutput:)
решает, что делать на основе ответа.
Если ответ является ответом HTTP с кодом 200, он просто возвращает данные. Обратите внимание, что он должен вернуть издателя, чей Failure
равен Error
, поэтому он использует Result.Pubilsher
и позволяет Swift выводить тип Failure
.
Если ответ HTTP-ответ с кодом 404 и 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()
}