DEV Community

Wesley de Groot
Wesley de Groot

Posted on • Originally published at wesleydegroot.nl on

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))
}
Enter fullscreen mode Exit fullscreen mode

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)
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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)
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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:_:)

Top comments (0)