DEV Community

Wesley de Groot
Wesley de Groot

Posted on • Originally published at wesleydegroot.nl on

async/await

Let's delve into the fascinating world of Swift's async/await.

This powerful feature, introduced in Swift 5.5, revolutionizes asynchronous programming, making it more intuitive and readable. Buckle up as we explore the ins and outs of async/await!

What is async/await?

At its core, async/await simplifies handling asynchronous tasks. It allows you to write asynchronous code that looks almost synchronous. Here's how it works:

  1. async Functions: You mark a function as async. Inside this function, you can use await to pause execution until an asynchronous operation completes.

  2. await Expression: When you encounter an await expression, the function suspends execution until the awaited task finishes. Meanwhile, other tasks can continue running concurrently.

Example 1: Fetching Data

Let's start with a common scenario: fetching data from a remote API. Imagine we have a callback-based function for fetching data:

func fetchData(completion: @escaping (Data?, Error?) -> Void) {
    let url = URL(string: "https://api.example.com/data")!
    URLSession.shared.dataTask(with: url) { data, _, error in
        completion(data, error)
    }.resume()
}
Enter fullscreen mode Exit fullscreen mode

Now, let's convert this to an async function using async/await:

func fetchData() async throws -> Data {
    let url = URL(string: "https://api.example.com/data")!
    return try await URLSession.shared.data(from: url).0
}
Enter fullscreen mode Exit fullscreen mode

In the async version:

  • We use try await to wait for the data to be fetched.
  • The error handling is cleaner, thanks to Swift's throws.

Example 2: Multiple API Calls

Imagine we have a callback-based function for fetching data:

func fetchUser(id: Int, completion: @escaping (User?, Error?) -> Void) {
    // Fetch user details...
}

func fetchPosts(for user: User, completion: @escaping ([Post]?, Error?) -> Void) {
    // Fetch posts for the user...
}

// Usage:

fetchUser(id: 1) { user, error in
  guard let user = user else {
    print(error)
  }
  // Guard for no error
  self.fetchPosts(for: user) { posts, error in
    // Process user and posts
  }
}
Enter fullscreen mode Exit fullscreen mode

Suppose we need to fetch a user and then fetch their posts. In the callback-based world, this can get messy. But with async/await, it's elegant:

func fetchUser(id: Int) async throws -> User {
    // Fetch user details...
}

func fetchPosts(for user: User) async throws -> [Post] {
    // Fetch posts for the user...
}

// Usage
Task {
    do {
        let user = try await fetchUser(id: 1)
        let posts = try await fetchPosts(for: user)
        // Process user and posts
    } catch {
        // Handle errors
    }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

By embracing async/await, you create a better development experience and write more efficient code. Say goodbye to callback hell and welcome a cleaner, more expressive way of handling asynchronous tasks in Swift. 🚀

Resources https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html#async-await

Top comments (0)