Async/await
Executing code asynchronously.
Async/await
Executing code asynchronously.
0
0
Checkbox to mark video as read
Mark as read

With the release of Swift 5.5, Apple introduced the async/await feature, a modern way of handling asynchronous operations. It greatly simplifies how we write asynchronous code by making it more readable and easier to maintain. The goal of async/await is to replace callback-based completion handlers and improve code flow by allowing developers to write asynchronous code that looks and behaves like synchronous code.

What is async/await?

Traditionally, asynchronous tasks in Swift were handled using closures, completion handlers, or libraries like GCD or Combine. While effective, these methods can make code difficult to read, especially when multiple asynchronous operations are chained, leading to the infamous "callback hell." Async/await resolves this by letting developers write asynchronous code in a linear fashion, as if it were synchronous, making it easier to follow and maintain.

The async keyword marks a function as asynchronous, meaning it can suspend its execution while waiting for a result. The await keyword is used when calling an async function to pause execution until that function returns a result.

Basic Syntax

Let's look at a simple example:


func fetchUserData() async -> User {
    // Simulates a network call to fetch user data
    let user = await getUserFromServer()
    return user
}

In this code, fetchUserData() is an asynchronous function, marked with the async keyword. The await keyword is used to indicate that the function must pause until getUserFromServer() completes and returns a value.

How Async/Await Works

The key idea behind async/await is that when an async function encounters an await expression, it suspends its execution until the awaited task completes. The rest of the function will resume execution once the async task finishes. This creates a cleaner, more synchronous-looking flow, even though the operations are non-blocking.

Example of Fetching Data

Consider an example where we fetch data from a server:


func fetchData() async throws -> Data {
    // Simulates the delay when getting data from server
    try await Task.sleep(nanoseconds: 1_000_000_000) // Sleep for 1 second
    return Data()
}

func processData() async {
    do {
        let data = try await fetchData()
        // Process the data here
        print("Data received: \(data)")
    } catch {
        print("Error fetching data: \(error)")
    }
}

In this example, fetchData() is an asynchronous function that fetches data from a server. The processData() function then awaits the result of fetchData() and handles the result or error. Note how we use do-try-catch blocks for error handling, as async functions can throw errors just like synchronous ones.

Concurrency with Async/Await

When working with multiple asynchronous tasks, you can await each one in sequence, or execute them concurrently using async let.

Sequential Execution

In sequential execution, each task is awaited before moving on to the next:


func fetchDataSequentially() async {
    let user = await fetchUserData()
    let posts = await fetchUserPosts(userId: user.id)
    let comments = await fetchComments(postId: posts.first!.id)
}

In this example, the code waits for each asynchronous function to complete before moving on to the next, executing in a predictable sequence.

Concurrent Execution

For tasks that don't depend on each other, you can run them concurrently with async let:


func fetchDataConcurrently() async {
    async let user = fetchUserData()
    async let posts = fetchUserPosts(userId: user.id)
    async let comments = fetchComments(postId: posts.first!.id)

    let (fetchedUser, fetchedPosts, fetchedComments) = await (user, posts, comments)
    print("User: \(fetchedUser), Posts: \(fetchedPosts), Comments: \(fetchedComments)")
}

Here, all three tasks are initiated at the same time, and await waits for them all to finish. This can significantly improve performance when the tasks are independent of each other.

Task

You can start a Task from a synchronous function, this is necessary if you want to call an asynchronous function from a synchronous one. We'll go into detail in upcoming articles, but for now let's see how to start a Task.

func syncFunc() {
    Task {
        await fetchDataConcurrently()
    }
}

Handling Task Cancellation

Asynchronous tasks in Swift can be cancelled. This is useful in scenarios where you might want to stop a task if it is no longer needed, such as when a user navigates away from a screen. You can check for cancellation by calling Task.checkCancellation() in your async function.


func performLongTask() async throws {
    for i in 1...10 {
        try Task.checkCancellation()
        print("Task iteration \(i)")
        try await Task.sleep(nanoseconds: 1_000_000_000) // Sleep for 1 second
    }
}

In this example, the task periodically checks whether it has been cancelled, allowing it to stop execution early if necessary.

course

Quiz Time!

0 Comments

Join the community to comment
Sign Up
I have an account
Be the first to comment

Accept Cookies

We use cookies to collect and analyze information on site performance and usage, in order to provide you with better service.

Check our Privacy Policy