I Promise this is a practical guide to Async / Await

lampewebdev

Michael "lampe" Lazarski

Posted on February 12, 2019

I Promise this is a practical guide to Async / Await

With ES8 we got another way to write code that is async in a more readable way then callback's its called Async / Await. With ES6 we already got Promises. To understand Async / Await we first have to understand Promises.

Promises

const resolveAfter2Seconds = () => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve();
    }, 2000);
  });
}

resolveAfter2Seconds()
    .then(() => { console.log('resolved') })        // this gets executed 
    .catch(() => { console.log('some error') });    // this does not get executed

const rejectAfter2Seconds = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject();
    }, 2000);
  });
}

rejectAfter2Seconds()
    .then(() => { console.log('resolved') })        // this does not get executed
    .catch(() => { console.log('some error') });    // this gets executed 

The resolveAfter2Seconds function will return a new Promise. Every promise has a state. The initial state is pending. After that, it can change to fulfilled or rejected. When it is fulfilled it will pass the value from the resolve to the then function you can then do whatever you want with it. If the state changes to rejected then it will run the catch() function. I hope the very basics of promises are now clear.

Question

Given the following code:

const resolveAfterXSeconds = (ms) => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(ms);
    }, ms);
  });
}

resolveAfterXSeconds(2000)
    .then((ms) => { console.log(`resolved after ${ms}`) });
resolveAfterXSeconds(1000)
    .then((ms) => { console.log(`resolved after ${ms}`) });

Will this code finish in approximately 2 seconds or 4 seconds? And after what time will we see the console.log()? So is this code sequential, concurrent or parallel?

Answer

This code is truly parallel. It will execute both functions and then return the second function call because the timeout is just 1000 ms and then the first because here the timeout is 2000. So you have to think if this is really what you want. Maybe these function calls depend on each other! So then this is not what you really wanted.

One solution I have seen to make this work is the following:

resolveAfterXSeconds(2000)
  .then((ms) => { 
    console.log('promise in the first then');

    resolveAfterXSeconds(1000).then((ms) => { console.log(`resolved after ${ms}`) })

    return ms;
  }).then((ms) => { console.log(`resolved after ${ms}`) });

We first call the function with 2000 and once it is resolved we then immediately call the function with 1000 and then we return the ms of the first function. a return is equal to a Promise.resolve(), that's why this is working here. So this would be run sequentially but it is not very readable and remindes me of the callback hell we wanted to avoid.

But what about Promise.all()? Let's have a look at an example:

Promise.all([resolveAfterXSeconds(2000), resolveAfterXSeconds(1000)]).then((ms) => {
  console.log(`resolved after ${ms[0]}`);
  console.log(`resolved after ${ms[1]}`);
});

This code is concurrent because Promise.all() creates a single Promise which is resolved when all the Promises it depends on are also resolved and because of that Both resolveAfterXSeconds functions are called at the same time but the then() function is called when all promises are fulfilled. You then will receive an array with the resolved promises. The array has each resolved value in the same order as the promises were passed to the Promise.all() function. This pattern is good if you have 2 API calls. One for the user data and one for the location information, for example, you can then compose them together to one object.

We will need all of this information to better understand Async / Await!

Async / Await

Let's finally move on to Async / Await! First things first: Async / Await is not a total replacement for Promises. Async / Await usually is easier to read but can be also easy misinterpreted. Our first example:

resolveAfterXSeconds = (ms) => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(ms);
    }, ms);
  });
}


start = async () => {
  const first = await resolveAfterXSeconds(2000);
  console.log(first);
  const second = await resolveAfterXSeconds(1000);
  console.log(second);
}
start();

So we are still using our old resolveAfterXSeconds function, nothing has changed here. Now we create a new function called start and here comes the first new thing the async before the arrow function. Only async () => {} will return an function. Calling this function will return a promise. Important to remember here is that if promise just returns something it will be fulfilled immediately. On the next line, we have something new too. await tells javascript that it has to wait here until the promise on the right side resolves or rejects until then this function will be paused. In our example the first call of the resolveAfterXSeconds function will take 2 seconds then it will run the console.log and then run the second resolveAfterXSeconds function. So it will take about 3 seconds to run our start function. Finally, we have what we wanted! async code that runs sequentially in javascript!

What we learn from this that Async / await is not the same as promise.then! This is important to keep in mind when coding. You have to use the right tool for the right job.

Async / Await can also be used in a concurrent style like promise.all.

resolveAfterXSeconds = (ms) => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(ms);
    }, ms);
  });
}

concurrentStart = async () => {

  const first = resolveAfterXSeconds(2000);
  const second = resolveAfterXSeconds(1000);

  console.log(await first);
  console.log(await second);
}

The only thing that has changed is that the await is now in the console.log() itself. Why is this concurrent now? because both first and second already started and now we are just waiting for both to finish because remember that async creates one promise. If you think back to Promise.all() then this example is exactly the same as this one.

Lets get practical

Fetch API

Let's have a look at the fetch API. fetch(URL) will return a new promise so we can await it but we are now dealing with network functions where we don't know if they ever resolve or if they are just rejected. So we need to deal with the errors.

fetchUserNames = async (endpoint) => {
  try {
    const response = await fetch(endpoint);
    let data = await response.json();
    return data.map(user => user.username);
  } catch (error) {
    throw new Error(error);
  }
}

start = async () => {
  userNames = await fetchUserNames('https://jsonplaceholder.typicode.com/users');
  console.log(userNames);
  fetchUserNames('https://wrong.url').catch(error => console.log(error));
}

start();

You can use Try / Catch in your Async / Await functions for better error handling. Just as a side note: nodejs will exit processes with uncatched errors! You can think of the return value here as the resolve and the throw as rejects in a promise. then we are using the fetch API for fetching data. as you see the fetch() call returns a promise. Because we know that we are getting a JSON we are calling .json() on the response which then itself returns a promise again for us that's why we need the await here too. Then we are just extracting the usernames and returning the newly created array. Our start function needs to be async because await only can be called in an async function. I'm mixing here on purpose await and promises to show you that you can use both!

koajs the new expressjs

app.get("/", async (request, response, next) => {
  try {
    const finalResult = await database.getStuff();
    response.json(finalResult);
  } catch (error) {
    next(error);
  }
});

If you ever have used expressjs you know what here is going on. koajs is by the same developers as expressjs but build from the ground up to use es6+ features. Also, it uses promises whenever it makes sense. In this example, we are handling an HTTP GET request on the '/' route. As you can see this rout can be async. Then we can do whatever we want in the arrow function. I the example you have to imagine that we are for example calling the database to get some data back and then send it back to the client.

running a function every X seconds

const waitFor = (ms) => new Promise(r => setTimeout(r, ms));

const start = async () => {
  try {
    console.log("start");
    c = 1;
    while(c < 10){
      console.log('waiting!');
      await waitFor(2000)
      console.log('start some function');
      await runDBBackup();
      if(c === 3){
        throw new Error('An error happend');
      }
      c++
    }
    console.log('end');
  } catch (error) {
    console.log(`error: #{error}`);
  }
}

start();

Okay here comes everything together what we learned. First, we need wrap setTimeout in a promise and it will resolve after an X amount of seconds. That's it! it does nothing more. It just pauses the execution. Then we are creating our start function. In this case, I made it fail on purpose after 3 runs. This is why we have the c variable. Then we will enter the while loop and wait for 2 seconds. Then we will run our backup function and when it runs the 4th time an error will happen. If you replace c < 10 with just true this will run as long as there is no exception. This is is an easy implementation of on backup process which will run after X amount of time.

Thanks for reading!

Say Hallo! Instagram | Twitter | LinkedIn | Medium

💖 💪 🙅 🚩
lampewebdev
Michael "lampe" Lazarski

Posted on February 12, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related