Async await in Swift explained

Reading Time: 5 minutes

Async await?

Async await is part of the new structured concurrency changes which were launched in Swift 5.5 during WWDC 2021. As we already know Concurrency in Swift means allowing multiple pieces of code to run at the same time, and is very important for the performance of our apps. With the new Async await, we can define methods performing work asynchronously.

Now that “Async await” is finally here, we can simplify our code with async methods and await statements and make our asynchronous code easier to read.

Async is?

Async is asynchronous and can be seen as a method attribute, which method performs asynchronous work. As an example we can use the method below:

func getProducts() async throws -> [Products] {
    // ... perform here data request
}

The getProducts method is defined as async throwing, which means that it’s performing a fail-able asynchronous job. The method would return an array of custom objects Product if everything went well or would throw an error if something went wrong.

How async can replace completion callbacks

Async methods replace the often seen completion callbacks. Completion callbacks (closure) were common in Swift to return from an asynchronous task, often combined with a Result type parameter. The above method would have been written as followed:

func getProducts(completion: (Result<[Product], Error>) -> Void) {
    // ... perform here data request
}

Creating a method with a completion closure is still possible in Swift, but it has a few disadvantages which are solved by using async instead:

  • Developer has to make sure to call the completion closure in each possible method exit, if not doing so will possibly result in an app waiting for a result infinitely.
  • Callbacks (closures) are harder to read. It’s not as easy to read about the order of execution as compared to how easy it is with structured concurrency.
  • Retain cycles has to be avoided using weak references.
  • Implementers has to switch over the result to get the outcome, also it’s not possible to use try catch statements.

These disadvantages are based on the closure version using the relatively new Result enum. It’s likely that a lot of projects still make use of completion callbacks without this enumeration:

func getProducts(completion: ([Product]?, Error?) -> Void) {
    // .. perform here data request
}

Defining a method like this makes it even harder to reason about the outcome on the caller’s side. Here value and error are optional, which requires us to perform an unwrap in any case. Unwrapping these optional results(value and error) in more code clutter does not help to improve readability.

How await works?

Await as the keyword stays to be used for calling async methods. Usually, we see them as best friends in Swift as one will never go without the other. You can basically say:

Await is awaiting a callback from its buddy async

Even though this sounds childish, it’s not a lie! If we take a look at an example by calling our earlier defined async throwing fetch products method:

do {
    let products = try await getProducts()
    print("Got \(products.count) products.")
} catch {
    print("Getting products failed with error \(error)")
}

We can note that the above code example is performing an asynchronous task. Using the await keyword, we tell our program to await a result from the getProducts method and only continue after a result arrives. This could either be an array of products or an error if anything went wrong while fetching the products.

What is structured concurrency?

Structured concurrency with async-await method calls makes it easier to understand the order of execution. Methods are linearly executed, one by one, without going back and forth like you would with closures.

To understand this better, we will take a look at how we would call the above code example before structured concurrency arrived:

// 1. Call the method

getProducts { result in

    // 3. The asynchronous method return

    switch result {

    case .success(let products):

        print(“Got \(products.count) products.”)

    case .failure(let error):

        print(“Getting products failed with error \(error)”)

    }

}

// 2. The calling method exits

As you can see, the calling method returns before the products are fetched. In case a result is received, we go back into our flow within the completion callback. This is an unstructured order of execution and could be hard to understand. This is especially true if we would perform another asynchronous method within our completion callback which would add another closure callback:

// 1. Call the method
getProducts { result in
    // 3. The asynchronous method return
    switch result {
    case .success(let products):
        print("Got \(products.count) products.")
        
        // 4. Call the placed method
        placedProducts(products) { result in
            // 6. Placed method returns
            switch result {
            case .success(let products):
                print("Decoded \(products) products.")
            case .failure(let error):
                print("Decoding products failed with error \(error)")
            }
        }
        // 5. Fetch products method returns
    case .failure(let error):
        print("Getting products failed with error \(error)")
    }
}
// 2. The calling method exits

Each completion callback (closure) adds another level of indentation, which makes it harder to follow the order of execution.

If we rewrite the above code using async-await syntax, we have a more readable piece of code, and also explains best what structured concurrency does:

do {
    // 1. Call the method
    let products = try await getProducts()
    // 2. Fetch products method returns
    
    // 3. Call the placed method
    let placedProducts = try await placedProducts(products)
    // 4. Placed method returns
    
    print("Got \(products.count) products.")
} catch {
    print("Getting products failed with error \(error)")
}
// 5. The calling method exits

The order of execution is linear, easy to follow and easy to reason about. Asynchronous calls will be easier to understand while we’re still performing sometimes complex asynchronous tasks.

Async/Await in Javascript

Reading Time: 7 minutes

Flow control in JS is hard, first of all, I’m gonna go with a quick review through promises because this is the foundation of Async + Await.

Promises

Promises in JS are like something is going to happen at some point in the future, so this could be

  • access to a user’s webcam
  • ajax call
  • resizing an image
  • or something else

All of these take time, and with promises, we kick in the process and move along, we come back when we need to deal with the data.

Let’s say we wanted to do a few things:

  • Learn Javascript
  • Write Javascript
  • Learn Vue
  • Write Vue

Would it make sense to first learn javascript before you start learning vue, would it make sense to wait until you learn javascript and start writing vue before you even start learning vue? No that doesn’t make sense.

We want to start one thing and come back to it once it’s done, and deal with the result correspondingly!

Promises allowed us to start writing code like this:

// start both things, one after another
const jsLearnPromise = learnJs();
const vueLearnPromise = learnVue();

// then once each are done, deal with them
jsLearnPromise.then(js => {
  writeJs();
})

vueLearnPromise.then(([vuex, vueRouter])=>{
  writeVue(vuex, vueRouter);
})


// you can also wait until both are done
Promise
 .all([jsLearnPromise, vueLearnPromise])
 .then(([js, vue]) => {
   codeWith(js, vue);
});

Most of the new browser APIs are built on promises, so we have got fetch(), where you can fetch your data then convert it into JSON and then finally deal with the data.

// fetch
fetch('http://learnprograming.org')
  .then(data => data.json())
  .then(vueFramework => learn(vue));

We can use a library called AXIOS, which have really good built-in defaults where we don’t have to have that second .then() chain like on the following example

//AXIOS fetch
axios.get('http://learnprograming.org')
  .then(vueFramework => learn(vue));

There are many many more browser APIs payment requests dealing with credit card data in the browser, getting user media, getting access to the webcam, web animations… All of these things are being built on standard promises and it’s easy to make your own with promises as well, here we have a function called sleep:

function sleep(amount) {
  return new Promise((resolve, reject)=> {
    if(amount <=300) {
      return reject('That is too fast, cool it down')
    }
    setTimeout(() => resolve(`Slept for ${amount}`), amount)
  });
}

where it takes the amount and the way of promise works is immediately you return a promise and then what you do inside of that promise is:

  • you either resolve it when things went well
    or
  • you reject it when things didn’t go well

In this case, after 500 milliseconds we’re going to resolve it with some data or if it’s less than 300 milliseconds I’m going to reject it because that’s too fast. And what that will allow us to do is a little bit something where we can write our code and then we can chain .then().then().then() on it and by returning a new promise from each one.

sleep(500)
  .then((result)=> {
  console.log(result)
  return sleep(1000);
})
  .then((result)=> {
  console.log(result)
  return sleep(750);
})
  .then((result)=> {
  console.log(result)
  console.log('done');
})

So promises are really really great but what’s the deal with the .then() ? It is still a kind of callback-y and any code that needs to come after the promise still needs to be in the final .then() callback, it can’t just be top-level in your current function, this is where ASYNC + AWAIT comes in.

Async/Await

Async await is still based on promises but it’s really a nice syntax to work with it.
JavaScript is almost entirely asynchronous/non-blocking.
Async await gives us synchronous looking code without the downside that is actually writing synchronous code! So how does it work? The first thing we need to do is mark our function as async so you still keep your regular promise functions, nothing changes with your functions that return a promise, what we now do is create an async function by writing the word “async” in front of it

async function sleep(){
//.....
}

then, when you are inside an async function, you simply just await things inside it, so you can either just await the sleep function and that will just wait until the promise resolves or if you care about what’s coming back from that promise, maybe it’s some data from an API then we can store that in a variable.

async function sleep() {
  //just wait
  await sleep(1000);
  //or capture the returned value
  const response = await sleep(750);
  console.log(response);
}

Let’s take a look at another example, in this case, I’m capturing the returned value, that’s another way you can write it.

const getDetails = async function() {
  const dave = await axios.get('https://api.github.com/users/dave');
  const john = await axios.get('https://api.github.com/users/john');
  const html = `
    <h1>${dave.name}</h1>
    <h1>${john.name}</h1>
  `;
}

in here I await axios.get() and when that comes back I am going to await the second axios again.
That is kind of slow, we don’t want to do that, so what we can do is we can simply await Promise.all() and by passing
Promise.all() to the other 2 promises, we sort of make one big mega promise that we can await for both of them to finish.

That was great but, if you have seen any examples online, the error handlings start to ugly it up. So let’s look at a couple of options that we can use for actually working with error handling.

Error handling

  • TRY/CATCH – this is probably likely what you have seen online, so just wrap everything in the try/catch and you gonna be nice and safe. The way it looks is:
aync function displayData() {
  try {
    const dave = await axios.get('https://api.github.com/users/dave');
    console.log(data) //Work with Data
  } catch (err) {
    console.log(err); //Handle Error
  }
}

You have an async function, you give yourself a try, write all your code inside that TRY, and then if anything happens inside that TRY, you catch the error in your catch(err) and you deal with that accordingly.

  • HIGHER ORDER FUNCTION where you can chain a .catch() on async functions. So this is little bit more complicated. Let’s walk through an example
// Create a function without any arror handling
aync function displayData() {
    //do something that errors out
    const dave = await axios.get('https://nothing.com');
}

you’ve got a function displayData() where I don’t care about error handling, I assume that everything works correctly and great, then I’m going to await something that maybe gives me a 404 and it’s going to break because no data came back, this could be any error that AXIOS might throw to you.

Now you create a higher-order function called handleError(err) that takes as an argument the actual function and then from that you return a new function, you basically return the same function but with a catch()

// make a function to handle that error
function handleEror(fn) {
  return function(...params) {
    return fn(...params).catch(function(err) {
      // do something with the error!
      console.error('Error', err);                           
    });
  }
}

The same function in one line using ES6:

const handleError = fn => (...params) => fn(...params).catch(console.error);

And then what you do is you just pass your unsafe function displayData to handleError(displayData)

// Wrap it in a HOC
const safeDisplayData = handleError(displayData);
safeDisplayData();
  • HANDLE THE ERROR WHEN YOU CALL IT, sometimes you need to handle the error when you call it because you say “This is a special case” if there’s an error here I need you to handle it in a different way, so it’s pretty simple, you make your async function called loadData() and maybe an error happens and when you call it, you can just chain .catch() on the end and then deal with the error.
async function loadData() {
  const dave = await axios.get('....');
}
loadData().catch(dealWithErrors);

Summary

In JavaScript, it is much cleaner to use async/await than to use promises with .then() and .catch() , async/await is a great way to deal with asynchronous behaviour and an excellent option if you find yourself writing long, complicated waterfalls of .then() statements.