How to create continuations that can throw errors
How to create continuations that can throw errors êŽë š
Updated for Xcode 15
Swift provides withCheckedContinuation()
and withUnsafeContinuation()
to let us create continuations that canât throw errors, but if the API youâre using can throw errors you should use their throwing equivalents: withCheckedThrowingContinuation()
and withUnsafeThrowingContinuation()
.
Both of these replacement functions work identically to their non-throwing counterparts, except now you need to catch any errors thrown inside the continuation.
To demonstrate this, hereâs the same fetchMessages()
function we used previously, built without async/await in mind:
struct Message: Decodable, Identifiable {
let id: Int
let from: String
let message: String
}
func fetchMessages(completion: @escaping ([Message]) -> Void) {
let url = URL(string: "https://hws.dev/user-messages.json")!
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
if let messages = try? JSONDecoder().decode([Message].self, from: data) {
completion(messages)
return
}
}
completion([])
}.resume()
}
If we wanted to wrap that using a continuation, we might decide that having zero messages is an error we should throw rather than just sending back an empty array. That thrown error would then need to be handled outside the continuation somehow.
So, first weâd define the errors we want to throw, then weâd write a newer async version of fetchMessages()
using withCheckedThrowingContinuation()
, and handling the âno messagesâ error using whatever code we wanted:
struct Message: Decodable, Identifiable {
let id: Int
let from: String
let message: String
}
func fetchMessages(completion: @escaping ([Message]) -> Void) {
let url = URL(string: "https://hws.dev/user-messages.json")!
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
if let messages = try? JSONDecoder().decode([Message].self, from: data) {
completion(messages)
return
}
}
completion([])
}.resume()
}
// An example error we can throw
enum FetchError: Error {
case noMessages
}
func fetchMessages() async -> [Message] {
do {
return try await withCheckedThrowingContinuation { continuation in
fetchMessages { messages in
if messages.isEmpty {
continuation.resume(throwing: FetchError.noMessages)
} else {
continuation.resume(returning: messages)
}
}
}
} catch {
return [
Message(id: 1, from: "Tom", message: "Welcome to MySpace! I'm your new friend.")
]
}
}
let messages = await fetchMessages()
print("Downloaded \(messages.count) messages.")
As you can see, that detects a lack of messages and sends back a welcome message instead, but you could also let the error propagate upwards by removing do
/catch
and making the new fetchMessages()
function throwing.
Tips
Using withUnsafeThrowingContinuation()
comes with all the same warnings as using withUnsafeContinuation()
â you should only switch over to it if itâs causing a performance problem.