Async/Await
Prerequisites: Networking with URLSession, Error handling
Some work takes time: network requests, reading large files, database queries. Asynchronous code lets a program keep doing other things — like keeping an app's interface responsive — while it waits.
Real-world analogy: ordering at a café. You place your order (start the task), step aside so others can order (the thread is free), and come back when your name is called (the result is ready).
async and await
asyncmarks a function that can suspend — pause while waiting, without blocking the thread it runs on.awaitmarks each point where suspension can happen. You can always see where your code might pause.
func fetchUser(id: Int) async -> String {
try? await Task.sleep(for: .seconds(1)) // stands in for a network call
return "User \(id)"
}
print("Fetching...")
let user = await fetchUser(id: 1)
print(user)
Fetching...
User 1
The code reads top to bottom like ordinary code, even though it waits a
second in the middle. Before async/await, the same thing needed
nested completion-handler closures, which were much harder to read
and easy to get wrong.
Throwing async functions
async combines with throws; call them with try await:
enum LoadError: Error { case notFound }
func loadProfile(id: Int) async throws -> String {
try await Task.sleep(for: .milliseconds(100))
guard id > 0 else { throw LoadError.notFound }
return "Profile \(id)"
}
do {
print(try await loadProfile(id: 7))
print(try await loadProfile(id: 0))
} catch {
print("Failed: \(error)")
}
Profile 7
Failed: notFound
Calling async code from synchronous code
You can only use await inside an async context. To start async work
from ordinary code — a button tap, for example — create a Task:
func buttonTapped() {
Task {
let user = await fetchUser(id: 42)
print("Loaded \(user)")
}
}
In SwiftUI, the .task { } view modifier starts async work when a view
appears and cancels it automatically when the view disappears.
Sequential by default
Each await waits for its result before the next line runs:
let a = await fetchUser(id: 1) // 1 second
let b = await fetchUser(id: 2) // another second
That takes two seconds. When the calls don't depend on each other, run them concurrently — the next lesson shows how.
Common mistakes
- Calling an async function without
await:expression is 'async' but is not marked with 'await'. - Wrapping everything in
Task { }to silence that error. Unstructured tasks aren't cancelled automatically; make the callerasyncinstead where you can.