Async Await in Swift with Code Examples

Swift now supports asynchronous (async) functions, enabling us to run complicated asynchronous code almost as if it were synchronous thanks to SE-0296. As in other languages like C# and JavaScript, this is accomplished in two steps: first, async functions are identified using the new `async` keyword, and then they are called with the await keyword. Let’s take a look at a few code examples to understand exactly how to use await and async in Swift.

Let’s start with a quick example so you can see what the syntax for async and await looks like in Swift - most of the code should be self-explanatory.

Async Await in Swift - Example

func fetchSomethingFromServer() async -> [Double] {
    // This can be a network request, a file data read, anything expensive really.
    (1...10000).map { _ in Double.random(in: -10...30) }

func average(for results: [Double]) async -> Double {
    let sum = results.reduce(0, +)
    let avg = sum / Double(results.count)
    return avg

func upload(result: Double) async -> String {
    // upload something to a server, this can be any expensive operation
    return "Done"

We’ve implemented 3 different async functions - notice the async keyword in their signature - this means that the blocks are dispatched to a background queue, and are run asynchronously.

Let’s see now how we can call these methods in order to actually process the results, and not just obtain a reference to the execution promise.

func processEverything() async {
    let results = await fetchSomethingFromServer()
    let avg = await average(for: results)
    let response = await upload(result: avg)
    print("Server response: \(response)")

As you can see, all of the indenting and closures have been removed, leaving what is frequently referred to as "straight-line code"; except from the await keywords, it resembles synchronous code. Because the execution is linear, it is simple to understand and reason about. While we're still doing occasionally difficult asynchronous jobs, it will be simpler to understand asynchronous programming, as using await / async in Swift is clearly a lot more readable.

There are a few simple, clear guidelines that govern how async functions operate:

  • Swift will generate an error if synchronized functions attempt to invoke async functions directly because that wouldn't make sense.
  • Async functions have the ability to call other async functions, but they can also, if necessary, call synchronous functions.
  • Swift will favor the function that best fits the current context if you have async and synchronous functions that can be called in the same way. If the call site is now async, Swift will call the async function; if not, it will call the synchronous function.

Calling Await from Synchronous Functions

You cannot use await in a synchronous environment. To be able to leverage async / await from a synchronous method, you can use Task.init like in the example below:

func fetchSomeData() {
        Task.init {
            do {
                self.results = try await fetchResults()
            } catch {
                // .. handle error

Async / Await with Try / Catch

Async functions and initializers now have the ability to throw errors when necessary thanks to the advent of async/await, which perfectly complements try/catch. The one exception is that Swift mandates a specific order for the keywords, and between call site and function, that order is inverted.

Here’s a code example of how to use async await in Swift within a try - catch block:

do {
    let data = try await fetchData()
    let processedData = try await process(data)
    print("Fetched \(processedData.count) results.")
} catch {
    print("Fetching data failed with error \(error)")

This is pretty much everything you need to know about async await in Swift. If you think there’s anything we missed, please let us know. Additionally, if you found this tutorial helpful, please share it on Twitter, to help us spread the word. Thanks!