Skip to content

Commit

Permalink
refactor async/await request error handling
Browse files Browse the repository at this point in the history
+ fix deprecated method withTaskCancellationHandler
  • Loading branch information
laqiguy committed Dec 19, 2023
1 parent 4fd175b commit f75650a
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 106 deletions.
108 changes: 57 additions & 51 deletions Sources/ApexyAlamofire/AlamofireClient+Concurrency.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,71 +12,77 @@ import Foundation
@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *)
extension AlamofireClient: ConcurrencyClient {

func observeResponse(
dataResponse: DataResponse<Data, AFError>,
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<T>(_ 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<T.Content>(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<T>(_ 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<T.Content>(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()
}
}
9 changes: 4 additions & 5 deletions Sources/ApexyURLSession/Helpers/AsyncAwaitHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,13 @@ enum AsyncAwaitHelper {

public static func adaptToAsync<T>(dataTaskClosure: (ContentContinuation<T>) -> 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<T>) in
let progress = dataTaskClosure(continuation)
progressWrapper.progress = progress
progressWrapper.progress = dataTaskClosure(continuation)
}
}, onCancel: {
progressWrapper.cancel()
})
}
}
93 changes: 43 additions & 50 deletions Sources/ApexyURLSession/URLSessionClient+Concurrency.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(_ 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<T>(_ 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
}
}
}

0 comments on commit f75650a

Please sign in to comment.