如果先前的请求未经授权,如何执行快速合并请求

时间:2021-04-05 12:56:51

标签: swift swiftui combine

我的出发点是这里的 foo() 示例之类的方法。我正在使用 retries: 1

    func foo() {

        // create url ...

        let urlRequest = URLRequest(for: url, httpMethod: "POST", resource: nil, token: accessToken)
        let combineRequest = CombineRequest(auth: auth)
        combineRequest.runWithResponseStatus(urlRequest, checkStatusCode: 201, retries: 1)
            .sink { completion in
                switch completion {
                case .finished:
                    break
                case .failure(let error):
                    self.toasts.insert(ToastText(type: .error, text: Text("\(error.localizedDescription)")), at: 0)
                    self.clearToasts()
                }
            } receiveValue: { _ in
                // successful
            }
            .store(in: &cancellables)
    }

所以这是一个 POST 合并请求 (runWithResponseStatus())。当我收到 401 - unauthorized 时,我想执行另一个合并请求 (refresh())。

  1. 如果 refresh() 成功,它应该执行 auth.setAccessToken(to: newAccessToken.token) 并重试它之前调用的 (runWithResponseStatus()) 方法一次。
  2. 现在,在 .failure 上,我正在执行 print("\(error.localizedDescription)"),但我想在链中返回该错误,以便它以 foo() 方法结束,在那里我可以将其放入 toasts 数组中。

有人知道如何实现这两点吗?如果可能的话,还可以利用联合收割机的力量让它更干净吗?

struct CombineRequest {
    
    var auth: Auth
    var jsonDecoder: JSONDecoder = JSONDecoder(dateFormatter: .isoDateFormatter)

    func runWithResponseStatus(_ request: URLRequest, checkStatusCode statusCode: Int = 200, queue: DispatchQueue = .main, retries: Int = 0) -> AnyPublisher<(data: Data, response: URLResponse), Error> {
        
        var cancellable = Set<AnyCancellable>()
        
        return URLSession.shared.dataTaskPublisher(for: request)
        .retry(retries)
        .tryMap { output in
            if let response = output.response as? HTTPURLResponse, response.statusCode != statusCode {
                switch response.statusCode {
                case 401:
                    try refresh(decodingType: AccessData.self)
                        .sink { completion in
                            switch completion {
                            case .finished:
                                break
                            case .failure(let error):
                                print("\(error.localizedDescription)")
                            }
                        } receiveValue: { newAccessToken in
                            auth.setAccessToken(to: newAccessToken.token)
                        }
                        .store(in: &cancellable)
                case 400:
                    throw ResourceError.internalServerError("General.error.general")
                default:
                    throw ResourceError.httpStatus(response.statusCode)
                }
            }
            return output
        }
        .receive(on: queue)
        .eraseToAnyPublisher()
    }


    private func refresh<DecodingType>(checkStatusCode statusCode: Int = 200, queue: DispatchQueue = .main, decodingType: DecodingType.Type) throws -> AnyPublisher<DecodingType, Error> where DecodingType: Decodable {
        
        // prepare all properties ...
        
        return URLSession.shared.dataTaskPublisher(for: refreshRequest)
            .tryMap { output in
                if let response = output.response as? HTTPURLResponse, response.statusCode != statusCode {
                    switch response.statusCode {
                    case 401:
                        auth.loggedOut()
                    case 400:
                        throw ResourceError.internalServerError("General.error.general")
                    default:
                        auth.loggedOut()
                        throw ResourceError.httpStatus(response.statusCode)
                    }
                }
                return output.data
            }
            .decode(type: DecodingType.self, decoder: jsonDecoder)
            .receive(on: queue)
            .eraseToAnyPublisher()
    }

}

1 个答案:

答案 0 :(得分:0)

我相信你可以catchreplaceError。尝试阅读:https://www.donnywals.com/catch-vs-replaceerror-in-combine/