Async Await
Awaiting async functions
Without return value
ts
// Defining an async function always returns a "promise"
async function querySomething(): Promise<void> {
// inside of an async function you can use await
await // ...
}
// in an async context you can await the execution
await querySomething()
swift
// Defining an async function
func querySomething() async -> Void {
// inside of an async function you can use await
await // ...
}
// in an async context you can await the execution
await querySomething()
With return value
ts
// Defining an async function always returns a "promise" that "resolves" with the return value
async function fetchSomething(): Promise<String> {
await // ...
return "data"
}
let result = await fetchSomething()
swift
// Defining an async function can be marked to return a value of eg. `String`
func fetchSomething() async -> String {
await // ...
return "data"
}
let result = await fetchSomething()
Try / catch
ts
// Any function _could_ throw, in TS this is not marked via syntax
async function querySomethingMightThrow(): Promise<void> {
await // ...
}
// await and capture errors with try/catch
try {
await querySomethingMightThrow()
} catch (error) {
console.error(error)
}
swift
// A function can only throw if explicitly marked with "throws"
func querySomethingMightThrow() async throws -> Void {
await // ...
}
// await and capture errors with do/try/catch
do {
try await querySomethingMightThrow()
} catch {
print(error)
}
Then / catch
ts
promise
.then((value) => { /** handle resolved value */ })
.catch((error) => { /** handle error message */ })
swift
// This doesn't really exist in Swift, use try/catch instead
Capturing promises
Without return value
ts
// Capture the promise in a variable to await later
const promise = querySomething()
// ...
await promise
swift
// an `async let` instance is similar to a TS `Promise` instance
// it's used for capturing a single async operation to await later
async let querying = querySomething()
// ...
await querying
// OR
// a `Task` instance is similar to a TS `Promise` instance
// you can do multiple async operations within the `Task` body
let task = Task { await querySomething() }
// ...
await task.value
With return value
ts
// Capture the promise in a variable to await later
const promise = fetchSomething()
// ...
let result = await promise
swift
// Capture an async let to await later
async let fetching = fetchSomething()
// ...
let result = await fetching
// OR
// Wrap in a Task to await later
let task = Task { await fetchSomething() }
// ...
let result = await task.value
Async IIFE
Creating an async context
ts
// Without a "top-level await" feature, create an async IIFE to create an async context in which you can await
(async () => {
await fetchSomething()
})()
swift
// create a Task to create an async context in which you can await
Task {
await fetchSomething()
}
Capturing the result
ts
const promise = (async () => {
// return something from the async IIFE
return await fetchSomething()
})()
const result = await promise
swift
let task = Task {
// return something from the Task
return await fetchSomething()
}
let result = await task.value
Shorter syntax with ()
ts
const result = await (async () => {
return await fetchSomething()
})()
swift
let result = await (Task {
return await fetchSomething()
}).value
Awaiting a Timeout
ts
await new Promise<void>((resolve) => setTimeout(resolve, 1_000))
swift
try? await Task.sleep(for: .milliseconds(1_000))
Promise.allSettled
Awaiting in parallel
ts
const promise1 = querySomething("a")
const promise2 = querySomething("b")
const promise3 = querySomething("c")
// `Promise.allSettled` will await all executions in parallel (and doesn't throw)
await Promise.allSettled([promise1, promise2, promise3])
swift
async let task1 = querySomething("a")
async let task2 = querySomething("b")
async let task3 = querySomething("c")
// a tuple of `async let` will await all executions in parallel
await (task1, task2, task3)
Awaiting a promise array in parallel
ts
const ids = ["a", "b", "c", /* ... */]
// `Promise.allSettled` will await all executions in parallel (and doesn't throw)
const promises = ids.map(async (id) => await querySomething(id))
await Promise.allSettled(promises)
swift
let ids = ["a", "b", "c", /* ... */]
// The simpelest way to await in parallel is to create an array of Tasks
let taskArray = ids.map { id in Task { await querySomething(id) } }
// The for...in loop here will await all executions in parallel, because the Tasks started executing upon creation
for task in taskArray { await task.value }
// OR
// `withTaskGroup` also awaits all executions in parallel, but has more controls for more advanced cases (eg. see Promise.all example)
await withTaskGroup(of: Void.self) { taskGroup in
for id in ids {
taskGroup.addTask { await querySomething(id) }
}
}
// passing `of: Void.self` to `withTaskGroup` means we don't return any values from the taskGroup
// (see next example for how to return values from a taskGroup)
Promise.all
Awaiting in parallel
ts
const promise1 = fetchSomething("a")
const promise2 = fetchSomething("b")
const promise3 = fetchSomething("c")
try {
// `Promise.all` will stop execution of all promises once one fails, rejects with the first error
const [value1, value2, value3] = await Promise.all([ promise1, promise2, promise3 ])
} catch (error) {
// first found error
}
swift
async let task1 = fetchSomethingMightThrow("a")
async let task2 = fetchSomethingMightThrow("b")
async let task3 = fetchSomethingMightThrow("c")
do {
// a tuple of `async let` will await all executions regardless if they throw or not, later it will throw the first error found
let (value1, value2, value3) = try await (task1, task2, task3)
} catch {
// first found error
}
Awaiting a promise array in parallel
ts
const ids = ["a", "b", "c", /* ... */]
const promises = ids.map(async (id) => await fetchSomething(id))
try {
// `Promise.all` will stop execution of all promises once one fails, rejects with the first error
const promises = Promise.all(promises)
const values = await promises
console.log(values)
} catch (error) {
// first found error
}
swift
let ids = ["a", "b", "c", /* ... */]
do {
// `withThrowingTaskGroup` might await execution of all tasks depending when one of them threw
// (eg. a network request in progress might still complete when another task threw during it)
// `withThrowingTaskGroup` throws the first error found
// (because of parralel execution we don't know which one)
async let tasks = withThrowingTaskGroup(
// define the type of whatever you return in `addTask`
of: (Int, String).self
) { taskGroup in
// prepare the results array with the same length as the number of tasks
var results: [String] = Array(repeating: "", count: ids.count)
// Use `.enumerated()` to grab both the index and the ID
for (index, id) in ids.enumerated() {
// these run in parallel, so we must return the index to keep track of the order
taskGroup.addTask { try (index, await fetchSomethingMightThrow(id)) }
}
// must use `for try await ... in` here
// even though this is a loop, these awaits run in parallel
for try await (index, result) in taskGroup {
results[index] = result
}
return results
}
let values = try await tasks
print(values)
} catch {
// first found error
}
Manual resolving / rejecting
ts
// return a `Promise` from a function to manually resolve or reject
function asyncFunction(): Promise<void> {
return new Promise((resolve, reject) => {
// resolve the promise
resolve("resolved value")
// OR
// reject the promise
reject("error message")
})
}
swift
enum CustomError: Error { case errorCase }
// return `withCheckedThrowingContinuation` from a function to manually return or throw
func asyncFunction() async throws {
try await withCheckedThrowingContinuation { continuation in
// resume the continuation and return
continuation.resume(returning: "resolved value")
// OR
// resume the continuation and throw
continuation.resume(throwing: CustomError.errorCase)
}
}