← thecodex.expert · The Codex Family of Knowledge
Swift

async/await

You genuinely cannot call an async function directly from ordinary synchronous code — Task { } is the real, required bridge that starts a new asynchronous context running.

Swift 6 Task { } bridges sync to async Last verified:
Canonical Definition

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.

Swiftbasic_async.swift
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.

Swifttask_bridge.swift
// 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.

Swiftasync_let.swift
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)
}

Sources

1
Apple / Swift.org. The Swift Programming Language, "Concurrency," docs.swift.org/swift-book.