From f75650aec881d36f393ba95f49dd5622ce88e6ac Mon Sep 17 00:00:00 2001 From: Aleksei Tiurnin Date: Tue, 19 Dec 2023 15:08:55 +0300 Subject: [PATCH] refactor async/await request error handling + fix deprecated method withTaskCancellationHandler --- .../AlamofireClient+Concurrency.swift | 108 +++++++++--------- .../Helpers/AsyncAwaitHelper.swift | 9 +- .../URLSessionClient+Concurrency.swift | 93 +++++++-------- 3 files changed, 104 insertions(+), 106 deletions(-) diff --git a/Sources/ApexyAlamofire/AlamofireClient+Concurrency.swift b/Sources/ApexyAlamofire/AlamofireClient+Concurrency.swift index 73b36be..335927d 100644 --- a/Sources/ApexyAlamofire/AlamofireClient+Concurrency.swift +++ b/Sources/ApexyAlamofire/AlamofireClient+Concurrency.swift @@ -12,71 +12,77 @@ import Foundation @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) extension AlamofireClient: ConcurrencyClient { - func observeResponse( - dataResponse: DataResponse, - error: Error?) { + private func observeResponse( + info: (request: URLRequest?, data: Data?, response: URLResponse?), + error: Error) { self.responseObserver?( - dataResponse.request, - dataResponse.response, - dataResponse.data, + info.request, + info.response as? HTTPURLResponse, + info.data, error) } open func request(_ endpoint: T) async throws -> T.Content where T : Endpoint { - let anyRequest = AnyRequest(create: endpoint.makeRequest) - let request = sessionManager.request(anyRequest) - .validate { request, response, data in - Result(catching: { try endpoint.validate(request, response: response, data: data) }) - } - - let dataResponse = await request.serializingData().response - let result = APIResult(catching: { () throws -> T.Content in - do { - let data = try dataResponse.result.get() - return try endpoint.content(from: dataResponse.response, with: data) - } catch { - throw error.unwrapAlamofireValidationError() + var info: (request: URLRequest?, data: Data?, response: URLResponse?) = (nil, nil, nil) + + do { + let anyRequest = AnyRequest(create: endpoint.makeRequest) + let request = sessionManager.request(anyRequest) + .validate { request, response, data in + Result(catching: { try endpoint.validate(request, response: response, data: data) }) + } + + info.request = request.request + + let dataResponse = await request.serializingData().response + + info.data = dataResponse.data + info.response = dataResponse.response + + let data = try dataResponse.result.get() + return try endpoint.content(from: dataResponse.response, with: data) + } catch { + Task.detached { [weak self, info] in + self?.observeResponse(info: info, error: error) } - }) - - Task.detached { [weak self, dataResponse, result] in - self?.observeResponse(dataResponse: dataResponse, error: result.error) + throw error } - - return try result.get() } open func upload(_ endpoint: T) async throws -> T.Content where T : UploadEndpoint { - let urlRequest: URLRequest - let body: UploadEndpointBody - (urlRequest, body) = try endpoint.makeRequest() + var info: (request: URLRequest?, data: Data?, response: URLResponse?) = (nil, nil, nil) - let request: UploadRequest - switch body { - case .data(let data): - request = sessionManager.upload(data, with: urlRequest) - case .file(let url): - request = sessionManager.upload(url, with: urlRequest) - case .stream(let stream): - request = sessionManager.upload(stream, with: urlRequest) - } - - let dataResponse = await request.serializingData().response - let result = APIResult(catching: { () throws -> T.Content in - do { - let data = try dataResponse.result.get() - return try endpoint.content(from: dataResponse.response, with: data) - } catch { - throw error.unwrapAlamofireValidationError() + do { + let urlRequest: URLRequest + let body: UploadEndpointBody + (urlRequest, body) = try endpoint.makeRequest() + + let request: UploadRequest + switch body { + case .data(let data): + request = sessionManager.upload(data, with: urlRequest) + case .file(let url): + request = sessionManager.upload(url, with: urlRequest) + case .stream(let stream): + request = sessionManager.upload(stream, with: urlRequest) } - }) - - Task.detached { [weak self, dataResponse, result] in - self?.observeResponse(dataResponse: dataResponse, error: result.error) + + info.request = request.request + + let dataResponse = await request.serializingData().response + + info.data = dataResponse.data + info.response = dataResponse.response + + let data = try dataResponse.result.get() + return try endpoint.content(from: dataResponse.response, with: data) + } catch { + Task.detached { [weak self, info] in + self?.observeResponse(info: info, error: error) + } + throw error } - - return try result.get() } } diff --git a/Sources/ApexyURLSession/Helpers/AsyncAwaitHelper.swift b/Sources/ApexyURLSession/Helpers/AsyncAwaitHelper.swift index 71211ba..0a2d30a 100644 --- a/Sources/ApexyURLSession/Helpers/AsyncAwaitHelper.swift +++ b/Sources/ApexyURLSession/Helpers/AsyncAwaitHelper.swift @@ -11,14 +11,13 @@ enum AsyncAwaitHelper { public static func adaptToAsync(dataTaskClosure: (ContentContinuation) -> Progress) async throws -> T { let progressWrapper = ProgressWrapper() - return try await withTaskCancellationHandler(handler: { - progressWrapper.cancel() - }, operation: { + return try await withTaskCancellationHandler(operation: { try Task.checkCancellation() return try await withCheckedThrowingContinuation { (continuation: ContentContinuation) in - let progress = dataTaskClosure(continuation) - progressWrapper.progress = progress + progressWrapper.progress = dataTaskClosure(continuation) } + }, onCancel: { + progressWrapper.cancel() }) } } diff --git a/Sources/ApexyURLSession/URLSessionClient+Concurrency.swift b/Sources/ApexyURLSession/URLSessionClient+Concurrency.swift index 547ad45..eaa40ab 100644 --- a/Sources/ApexyURLSession/URLSessionClient+Concurrency.swift +++ b/Sources/ApexyURLSession/URLSessionClient+Concurrency.swift @@ -11,80 +11,73 @@ import Foundation @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) extension URLSessionClient: ConcurrencyClient { - func observeResponse( - request: URLRequest?, - responseResult: Result<(data: Data, response: URLResponse), Error>) { - let tuple = try? responseResult.get() + private func observeResponse( + info: (request: URLRequest?, data: Data?, response: URLResponse?), + error: Error) { self.responseObserver?( - request, - tuple?.response as? HTTPURLResponse, - tuple?.data, - responseResult.error) + info.request, + info.response as? HTTPURLResponse, + info.data, + error) } open func request(_ endpoint: T) async throws -> T.Content where T : Endpoint { - var request = try endpoint.makeRequest() - request = try requestAdapter.adapt(request) - var responseResult: Result<(data: Data, response: URLResponse), Error> + var info: (request: URLRequest?, data: Data?, response: URLResponse?) = (nil, nil, nil) do { - let response: (data: Data, response: URLResponse) = try await session.data(for: request) + var request = try endpoint.makeRequest() + request = try requestAdapter.adapt(request) - if let httpResponse = response.response as? HTTPURLResponse { - try endpoint.validate(request, response: httpResponse, data: response.data) + info.request = request + + let result: (data: Data, response: URLResponse) = try await session.data(for: request) + + info.data = result.data + info.response = result.response + + if let httpResponse = result.response as? HTTPURLResponse { + try endpoint.validate(request, response: httpResponse, data: result.data) } - responseResult = .success(response) - } catch let someError { - responseResult = .failure(someError) - } - - Task.detached { [weak self, request, responseResult] in - self?.observeResponse(request: request, responseResult: responseResult) - } - - return try responseResult.flatMap { tuple in - do { - return .success(try endpoint.content(from: tuple.response, with: tuple.data)) - } catch { - return .failure(error) + return try endpoint.content(from: result.response, with: result.data) + } catch { + Task.detached { [weak self, info] in + self?.observeResponse(info: info, error: error) } - }.get() + throw error + } } open func upload(_ endpoint: T) async throws -> T.Content where T : UploadEndpoint { - var request: (request: URLRequest, body: UploadEndpointBody) = try endpoint.makeRequest() - request.request = try requestAdapter.adapt(request.request) - var responseResult: Result<(data: Data, response: URLResponse), Error> + var info: (request: URLRequest?, data: Data?, response: URLResponse?) = (nil, nil, nil) do { - let response: (data: Data, response: URLResponse) + var request: (request: URLRequest, body: UploadEndpointBody) = try endpoint.makeRequest() + request.request = try requestAdapter.adapt(request.request) + + info.request = request.request + + let result: (data: Data, response: URLResponse) switch request { case (_, .data(let data)): - response = try await session.upload(for: request.request, from: data) + result = try await session.upload(for: request.request, from: data) case (_, .file(let url)): - response = try await session.upload(for: request.request, fromFile: url) + result = try await session.upload(for: request.request, fromFile: url) case (_, .stream): throw URLSessionClientError.uploadStreamUnimplemented } - responseResult = .success(response) - } catch let someError { - responseResult = .failure(someError) - } - - Task.detached { [weak self, request, responseResult] in - self?.observeResponse(request: request.request, responseResult: responseResult) - } - - return try responseResult.flatMap { tuple in - do { - return .success(try endpoint.content(from: tuple.response, with: tuple.data)) - } catch { - return .failure(error) + info.data = result.data + info.response = result.response + + return try endpoint.content(from: result.response, with: result.data) + } catch { + Task.detached { [weak self, info] in + self?.observeResponse(info: info, error: error) } - }.get() + throw error + } } }