Skip to content
Theory · Asynchronous code · lesson 5 of 7

At the same time: async let

Now you order tea and coffee at once, from two baristas, and get two buzzers. The drinks are made at the same time, and you wait as long as the slowest of them takes.

Two awaits in a row cannot do that: the second call starts only when the first has ended. For two jobs to run at the same time, Swift has async let:

SWIFT
func brew(_ drink: String, ms: Int) async throws -> String {
    try await Task.sleep(for: .milliseconds(ms))
    return drink
}

async let tea = brew("tea", ms: 100)
async let coffee = brew("coffee", ms: 50)
let (first, second) = try await (tea, coffee)
print(first, second) // tea coffee

Two jobs at once

async let declares a constant whose value starts being computed right away and is read later. Let's go through the program line by line:

  1. async let tea = brew("tea", ms: 100) starts making the tea and moves on at once, without waiting for the result.
  2. async let coffee = brew("coffee", ms: 50) starts the coffee. Now both jobs run at the same time, and each of them is a task: Swift runs it separately from the rest of the code.
  3. try await (tea, coffee) waits for both drinks and puts them into the constants first and second. try is needed because brew may throw an error.
  4. print(first, second) prints tea coffee.

The tea and coffee pauses run at the same time, so the program waits about as long as the tea takes, the slowest drink: around 100 ms. The same two calls with await in a row would wait for the sum of the pauses, at least 150 ms.

Where await and try stand in this program:

  • the line with async let needs neither await nor try: the job only starts there, and an error, if one happens, arrives where the value is read;
  • try await is needed where the value is read: if the job has not finished yet, the program waits for it on that line.

The order of the results

The coffee takes 50 ms, half as long as the tea. Even so, the program prints tea coffee, not coffee tea. The code sets the order of the results: in (tea, coffee) the tea comes first, and which job finished earlier does not change that.

It is the same with buzzers: whichever goes off first, you put the cups on the tray in the order you chose yourself.

Here the picture of two baristas stops working. A café always has two baristas, but the computer decides for itself how many jobs to run at the very same instant. Swift promises something else: both jobs start at once, and neither waits for the other to end.

Interviewers like to ask this: what runs at the same time in such code? The answer:

  • jobs started with async let run at the same time;
  • calls with await in a row run in turn, however many there are;
  • the value of an async let is still read with await, and on that line the program waits until the job ends.

Common mistake

Two jobs were started with await in a row, expecting them to run at the same time:

SWIFT
let cupOfTea = try await brew("tea", ms: 100) // ✗
let cupOfCoffee = try await brew("coffee", ms: 50)
print(cupOfTea, cupOfCoffee)

The program prints the same tea coffee, but it waits at least 150 ms instead of about 100: the coffee starts only when the tea is ready. The compiler shows no error, because the program works correctly, only slower. The fix is async let, as in the example above.

Key points

  • async let starts a job at once and does not wait for it; you wait where you read the value with await.
  • Jobs started with async let run at the same time, and the whole wait is about as long as the slowest of them.
  • The code sets the order of the results, not which job finished first.
  • Two awaits in a row run in turn, not at the same time.