FREE TRIAL: Accelerate your app development career with Hacking with Swift+! >>

How to create continuations that can throw errors

Paul Hudson    @twostraws   

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:

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:

// 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.")
        ]
    }
}

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.

Tip: 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.

Hacking with Swift is sponsored by Essential Developer

SPONSORED From August 2nd to 8th you can join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer!

Save your spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

BUY OUR BOOKS
Buy Pro Swift Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift (Vapor Edition) Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Server-Side Swift (Kitura Edition) Buy Beyond Code

Was this page useful? Let us know!

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.