Async/Await Basics
Async/Await Basics
Section titled “Async/Await Basics”Use this when:
- You are starting fresh with async/await and need foundational patterns.
- You are converting callback-based code to async/await.
- You need to understand execution order and the sync-to-async bridge.
Skip this file if:
- You need parallel execution with task groups or
async let. Usetasks.md. - You need stream-based async iteration. Use
async-sequences.md.
Jump to:
- Function Declaration
- Execution Order
- Parallel Execution with async let
- URLSession with Async/Await
- Migration Strategy
Function Declaration
Section titled “Function Declaration”Mark functions with async to indicate asynchronous work:
func fetchData() async -> Data { // async work}
func fetchData() async throws -> Data { // async work that can fail}Key benefit over closures: The compiler enforces return values. No forgotten completion handlers.
Calling Async Functions
Section titled “Calling Async Functions”From synchronous context
Section titled “From synchronous context”Use Task to bridge from sync to async:
Task { let data = try await fetchData()}From async context
Section titled “From async context”Use await directly:
func processData() async throws { let data = try await fetchData() // process data}Execution Order
Section titled “Execution Order”Structured concurrency executes top-to-bottom in the order you expect:
let first = try await fetchData(1) // Waits for completionlet second = try await fetchData(2) // Starts after first completeslet third = try await fetchData(3) // Starts after second completesCode after await only executes once the awaited function returns.
Parallel Execution with async let
Section titled “Parallel Execution with async let”Use async let to run multiple operations concurrently:
async let data1 = fetchData(1)async let data2 = fetchData(2)async let data3 = fetchData(3)
let results = try await [data1, data2, data3]How async let works
Section titled “How async let works”- Starts immediately: The function executes right away, even before
await - Structured concurrency: Automatically canceled when leaving scope
- Error handling: If one fails, others are implicitly canceled when awaiting grouped results
- No redundant keywords: Don’t use
try awaitin theasync letline itself
// Redundant - avoid thisasync let data = try await fetchData()
// Correct - errors handled at await pointasync let data = fetchData()let result = try await dataWhen to use async let
Section titled “When to use async let”Use when:
- Tasks don’t depend on each other
- Number of tasks known at compile-time
- Want automatic cancellation on scope exit
Avoid when:
- Tasks must run sequentially
- Need dynamic task spawning (use
TaskGroup) - Need manual cancellation control
Limitations
Section titled “Limitations”- Cannot use at top-level declarations (only within function bodies)
- Tasks not explicitly awaited may be canceled implicitly
URLSession with Async/Await
Section titled “URLSession with Async/Await”URLSession provides async alternatives to closure-based APIs:
// Closure-based (old)URLSession.shared.dataTask(with: request) { data, response, error in guard let data = data, error == nil else { return } // handle response}.resume()
// Async/await (modern)let (data, response) = try await URLSession.shared.data(for: request)Benefits over closures
Section titled “Benefits over closures”- No optional
dataorresponseto unwrap - Automatic error throwing
- Compiler enforces return values
- Simpler error handling with do-catch
Complete network request pattern
Section titled “Complete network request pattern”func fetchUser(id: Int) async throws -> User { let url = URL(string: "https://api.example.com/users/\(id)")! var request = URLRequest(url: url) request.httpMethod = "GET"
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else { throw NetworkError.invalidResponse }
return try JSONDecoder().decode(User.self, from: data)}POST requests with JSON
Section titled “POST requests with JSON”func createUser(_ user: User) async throws -> User { let url = URL(string: "https://api.example.com/users")! var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.httpBody = try JSONEncoder().encode(user)
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else { throw NetworkError.invalidResponse }
return try JSONDecoder().decode(User.self, from: data)}Typed Errors (Swift 6)
Section titled “Typed Errors (Swift 6)”Specify exact error types for better API contracts:
enum NetworkError: Error { case invalidResponse case decodingFailed(DecodingError) case requestFailed(URLError)}
func fetchData() async throws(NetworkError) -> Data { do { let (data, _) = try await URLSession.shared.data(from: url) return data } catch let error as URLError { throw .requestFailed(error) } catch { throw .invalidResponse }}Callers know exactly which errors to handle.
Migration Strategy
Section titled “Migration Strategy”When converting closure-based code:
- Add new async method alongside old one - keeps code compiling
- Update method signature - add
async, remove completion parameter - Replace closure calls with await - use URLSession async APIs
- Remove optional unwrapping - async APIs return non-optional values
- Simplify error handling - use do-catch instead of nested closures
- Return directly - compiler enforces return values
Common Patterns
Section titled “Common Patterns”Sequential execution (when order matters)
Section titled “Sequential execution (when order matters)”let user = try await fetchUser(id: 1)let posts = try await fetchPosts(userId: user.id)let comments = try await fetchComments(postIds: posts.map(\.id))Parallel execution (when independent)
Section titled “Parallel execution (when independent)”async let user = fetchUser(id: 1)async let settings = fetchSettings()async let notifications = fetchNotifications()
let (userData, settingsData, notificationsData) = try await (user, settings, notifications)Mixed execution
Section titled “Mixed execution”// Fetch user first (required for next step)let user = try await fetchUser(id: 1)
// Then fetch related data in parallelasync let posts = fetchPosts(userId: user.id)async let followers = fetchFollowers(userId: user.id)async let following = fetchFollowing(userId: user.id)
let profile = Profile( user: user, posts: try await posts, followers: try await followers, following: try await following)