I wish to throw errors and/or return request results from a closure. I went through some posts on SO and looks like people are mainly passing completion blocks to propogate error and result to the calling function.
I have a chain of functions (sometimes 3 function chain and sometimes 4-5), which means I will have to pass completion blocks to every function. Am trying to figure out if there is a better way to return result and errors from closure within a function.
BTW I am using MSGraph API here.
Code – Request functions:
private func processRequest(_ request: NSMutableURLRequest, completion: @escaping(Data?, URLResponse?, Error?) throws -> ()) rethrows {
let dataTask: MSURLSessionDataTask = httpClient.dataTask(with: request, completionHandler: { (data, response, error) in
if let response = response as? HTTPURLResponse {
let statusCode = response.statusCode
if (statusCode < 200) || (statusCode > 299) {
let description = "Microsoft Graph service returned HTTP response status (statusCode) for URL (request.url?.absoluteString ?? "")"
let error = NSError(code: statusCode, localizedDescription: description)
try? completion(nil, nil, error)
}
}
if let error = error {
try? completion(nil, nil, error)
}
try? completion(data, response, nil)
})
dataTask.execute()
}
private func processRequestWithToken(_ request: NSMutableURLRequest, completion: (Data?, URLResponse?) -> ()) async throws {
try await MicrosoftAccount.acquireToken()
try processRequest(request) { (data, response, error) in
if let error = error {
throw error
}
completion(data, response)
}
}
Code – Calling functions:
This below functions throws an error – Cannot convert value of type {‘Bool’ or ‘[MSGraphEvent]’} to closure result type ‘()’, obviously because I am not returning a completion block.
public func cancelBooking(eventId: String) async throws -> Bool {
let request = makeRequest(relativeUrl: "/me/events/(eventId)/cancel", httpMethod: "POST")
addStandardRequestHeaders(to: request)
self.processRequestWithToken(request) { data, response in
if let response = response as? HTTPURLResponse, response.statusCode == 202 {
return true
} else {
return false
}
}
}
public func getEvents(selectedDate: Date?) async throws -> [MSGraphEvent] {
var api = "/me/calendar/events?"
let request = self.makeRequest(relativeUrl: api, httpMethod: "GET")
self.processRequestWithToken(request) { data, response in
let events: [MSGraphEvent] = try self.parseCollection(data: data)
return events
}
}
CancelBooking and getEvents functions are then called by another function. If I return a completion block to them, then I don’t see any errors and everything works fine, but I have to pass completion block to every function in the chain.
Is there a better way I can propagate the results up in the function chain without passing completion block as parameters for every function?
2
Answers
I came across DispatchGroup and that's one way which helped me to solve my problem. Sharing the code for it below.
I will also try Joakim's suggestion and will share it's code as well.
This problem of chaining completion handlers is often called "Callback Hell" or "The Pyramid of Doom" and is specifically addressed by the Async/Await paradigm.
A challenge to answering your question is that the answer is simply "learn to use async/await" and apply it to your problem domain.
I recommend you start with "Meet async/await in Swift" which describes the pyramid of doom and how async/await addresses the issue:
https://developer.apple.com/videos/play/wwdc2021/10132/