lukecsmith.co.uk

Explaining withCheckedThrowingContinuation

Luke, 15 Aug 24

Introduction

Before Async Await there was callbacks. This was the mainstay of async code in swift for a long time, and doing a quick search of my old repos, this pattern is absolutely everywhere. Heres an example:

func fetchData(completion: @escaping (Result<String, Error>) -> Void) {
    // Simulate a network call
    DispatchQueue.global().async {
        // Simulate a delay
        sleep(2)
        
        // Simulate a successful response
        let data = "Fetched Data"
        completion(.success(data))
        
        // If there was an error, you could call:
        // completion(.failure(SomeError()))
    }
}

So you pass a completion handler into a function, which itself is a closure - a block of code passed as a parameter. This block of code is executed, passing in the result, with the line:

completion(.success(data))

Incidentally, its marked @escaping to allow the code in the completion closure to be executed after the function has been returned- it also allows Swift to handle the memory allocated to this closure correctly. Without @escaping, this closure might be deallocated before it has been asynchronously executed.

Async Await is an improvement on the completion handler pattern in various ways. But what if you cant or dont want to rewrite that function to use await directly, to make it fit in the new async await world? You can wrap the function instead using withCheckedThrowingContinuation, while keeping the older one unchanged. Heres a new function that does that:

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

And now you can call the original function, via the new wrapped function, using async await properly:

func performDataFetch() async {
    do {
        let data = try await fetchDataAsync()
        print("Received data: \(data)")
    } catch {
        print("Failed to fetch data: \(error)")
    }
}
Tagged with: