Wes Matlock

Embracing Swift’s Async/Await: Bridging with Existing Closures

Swift’s async/await syntax, introduced in Swift 5.5, provides a powerful way to handle asynchronous code in a more readable and manageable…


Embracing Swift’s Async/Await: Bridging with Existing Closures

Swift’s async/await syntax, introduced in Swift 5.5, provides a powerful way to handle asynchronous code in a more readable and manageable manner. However, many existing APIs and legacy codebases still use closure-based async patterns. This blog post will guide you through integrating async/await with existing closures, enabling a seamless transition and hybrid usage.

Understanding the Basics

Before diving into integration, let’s briefly recap the basics of async/await in Swift.

Async Function:

An async function allows you to perform asynchronous operations in a sequential manner.

func fetchData() async -> Data {  
    // Simulate a network request  
    return Data()  
}

Await Keyword:

The await keyword is used to call async functions.

let data = await fetchData()

Converting a Closure-Based Function to Async/Await

Consider a typical closure-based function that fetches data from a network:

func fetchData(completion: @escaping (Data?, Error?) -> Void) {  
    // Simulate a network request  
    DispatchQueue.global().async {  
        // Assume data fetched  
        let data = Data()  
        completion(data, nil)  
    }  
}

To use this function with async/await, we need to wrap it in an async function. We can achieve this by leveraging withCheckedContinuation.

func fetchDataAsync() 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)  
            } else {  
                continuation.resume(throwing: URLError(.badServerResponse))  
            }  
        }  
    }  
}

Here’s a breakdown of what’s happening:

• withCheckedThrowingContinuation creates a continuation that we can resume later.

• We call the original closure-based function within this block.

• Inside the closure, we handle the success and failure cases by resuming the continuation accordingly.

Using the Converted Function

Now, let’s see how we can use this newly created async function.

Task {  
    do {  
        let data = try await fetchDataAsync()  
        print("Data fetched: \(data)")  
    } catch {  
        print("Error fetching data: \(error)")  
    }  
}

Hybrid Approach: Using Async/Await and Closures Together

In some cases, you might need to call an async function but still support the closure-based API. Here’s how you can create a function that supports both:

func fetchData(completion: @escaping (Data?, Error?) -> Void) {  
    if #available(iOS 15.0, *) {  
        Task {  
            do {  
                let data = try await fetchDataAsync()  
                completion(data, nil)  
            } catch {  
                completion(nil, error)  
            }  
        }  
    } else {  
        // Fallback on earlier versions  
        fetchData { data, error in  
            completion(data, error)  
        }  
    }  
}

In this example:

• We check if the async/await feature is available.

• If available, we use Task to call the async function and handle the result using the completion handler.

• If not available, we fall back to the original closure-based implementation.

Conclusion

Swift’s async/await syntax significantly improves the readability and maintainability of asynchronous code. By wrapping existing closure-based functions with async/await, you can gradually adopt this modern approach while maintaining compatibility with existing code. This hybrid approach ensures a smooth transition and leverages the best of both worlds.

By integrating async/await with existing closure-based code, you can modernize your Swift codebase without breaking compatibility, paving the way for more readable and maintainable asynchronous code.

Happy coding! 🚀

By Wesley Matlock on May 23, 2024.

Canonical link

Exported from Medium on May 10, 2025.

Written on May 23, 2024