Back

Wesley de Groot's Blog
Translating closures to async

Translating closures to async

In this post, we will discuss how to translate closures to async in Swift.

What is continuation?

In Swift, a continuation is a construct that allows you to suspend and resume execution of a piece of code at a later time. It's often used in the context of asynchronous programming.

When you're dealing with asynchronous tasks, you often need to wait for some operation to complete before you can continue with the rest of your code. This is where continuation comes in.

You can think of a continuation as a placeholder for the future result of an asynchronous operation. When the operation is complete, you can "resume" the continuation with the result, allowing the rest of your code to proceed.

In Swift 5.5 and later, continuation is often used in the implementation of async/await patterns. For example, you might use a continuation to wrap a callback-based asynchronous API into a function that can be used with await.

Use Case: continuation

Imagine you have a function that takes a callback as an argument, and you want to convert it to an async function. Here's an example of what that might look like:

func fetchData(completion: @escaping (Result<Data, Error>) -> Void) {
    // Perform some asynchronous operation
    // ...
    // When the operation is complete, call the completion handler
    completion(.success(data))
}

This function takes a completion handler as an argument, and when the asynchronous operation is complete, it calls the completion handler with the result.

To convert this function to an async function, you can use a continuation. Here's how you might do that:

func fetchData() async throws -> Data {
    return try await withCheckedThrowingContinuation { continuation in
        fetchData { result in
            switch result {
            case .success(let data):
                continuation.resume(returning: data)
            case .failure(let error):
                continuation.resume(throwing: error)
            }
        }
    }
}

Example 2:

Imagine we have a function whihout Result type, and we want to convert it to an async function. Here's an example of what that might look like:

func fetchData(completion: @escaping (Data?, Error?) -> Void) {
    // Perform some asynchronous operation
    // ...
    // When the operation is complete, call the completion handler
    completion(data, error)
}

To convert this function to an async function, you can use a continuation. Here's how you might do that:

func fetchData() async throws -> Data {
    return try await withCheckedThrowingContinuation { continuation in
        fetchData { data, error in
            if let data = data {
                continuation.resume(returning: data)
            } else if let error = error {
                continuation.resume(throwing: error)
            }
        }
    }
}

withCheckedThrowingContinuation vs withUnsafeContinuation

withCheckedContinuation will add runtime checks to detect any improper use of the continuation and warn you if it were to happen. withUnsafeContinuation will perform none of these checks.

Caveats

continuation must be called exactly once.

Wrap up

Continuations are a powerful tool for working with asynchronous code in Swift. They allow you to suspend and resume execution of a piece of code at a later time, making it easier to work with asynchronous operations.

Resources: https://developer.apple.com/documentation/swift/checkedcontinuation
https://developer.apple.com/documentation/swift/withcheckedcontinuation(function:_:)

x-twitter mastodon github linkedin discord threads instagram whatsapp bluesky square-rss sitemap