Skip to main content

How to create continuations that can throw errors

About 3 minSwiftArticle(s)bloghackingwithswift.comcrashcourseswiftxcodeappstore

How to create continuations that can throw errors 관련

Swift Concurrency by Example

Back to Home

How to create continuations that can throw errors | Swift Concurrency by Example

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.")

Download this as an Xcode projectopen in new window

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.

Similar solutions…
How to use continuations to convert completion handlers into async functions | Swift Concurrency by Example

How to use continuations to convert completion handlers into async functions
How to store continuations to be resumed later | Swift Concurrency by Example

How to store continuations to be resumed later
What is an actor and why does Swift have them? | Swift Concurrency by Example

What is an actor and why does Swift have them?
What’s the difference between actors, classes, and structs? | Swift Concurrency by Example

What’s the difference between actors, classes, and structs?
How to create and use task local values | Swift Concurrency by Example

How to create and use task local values

이찬희 (MarkiiimarK)
Never Stop Learning.