func fetchData() async throws -> Data marks a function as able to suspend during its execution; calling it requires await at the call site, marking where a suspension might genuinely occur. Critically, async functions can only be called from an asynchronous context — ordinary synchronous code (like a button's action handler) cannot call one directly, and needs Task { await fetchData() } to bridge into a new async context. async let declares a concurrent binding: multiple async let statements start their operations concurrently, with each one's actual result only awaited (and its completion actually blocked on) when the value is later read.
Declaring and calling an async function
await marks the call site where processOrder might actually suspend while fetchUser runs — execution doesn't block a thread during that suspension, it simply yields control back until the result is ready.
func fetchUser(id: Int) async throws -> String {
// simulated network delay
try await Task.sleep(nanoseconds: 1_000_000_000)
return "User \(id)"
}
func processOrder() async throws {
let user = try await fetchUser(id: 1) // 'await' marks the suspension point
print("Processing order for \(user)")
}Task { }: the required bridge from sync code
processOrder() cannot be called directly from ordinary code — Task { } is what actually starts a new asynchronous context, letting await appear inside its closure.
// processOrder() // COMPILE ERROR: cannot call an async function from sync context directly
Task {
try await processOrder() // Task{} bridges into the async world
}async let: structured concurrency for multiple operations
Both fetchUser calls start running CONCURRENTLY at the async let lines — the actual await only blocks for each result at the point it's genuinely needed, letting both operations overlap rather than running one after the other.
func loadBothUsers() async throws {
async let user1 = fetchUser(id: 1) // starts running concurrently
async let user2 = fetchUser(id: 2) // ALSO starts running concurrently
let results = try await [user1, user2] // awaits both — ran concurrently (overlapping);
// whether that was also literally parallel (simultaneous
// on separate cores) depends on the cooperative thread
// pool and core count -- Swift guarantees concurrency,
// not parallelism
print(results)
}