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:
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 coffeeTwo 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:
async let tea = brew("tea", ms: 100)starts making the tea and moves on at once, without waiting for the result.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.try await (tea, coffee)waits for both drinks and puts them into the constantsfirstandsecond.tryis needed becausebrewmay throw an error.print(first, second)printstea 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 letneeds neitherawaitnortry: the job only starts there, and an error, if one happens, arrives where the value is read; try awaitis 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 letrun at the same time; - calls with
awaitin a row run in turn, however many there are; - the value of an
async letis still read withawait, 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:
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 letstarts a job at once and does not wait for it; you wait where you read the value withawait.- Jobs started with
async letrun 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.