DEV Community

Cover image for I Promise this is a practical guide to Async / Await
Michael "lampe" Lazarski
Michael "lampe" Lazarski

Posted on

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

Top comments (8)

Collapse
 
namirsab profile image
Namir

Hey Micha!
Nice post!

There is a better way to chain promises one after another. You can just return a promise within a then callback, like this:

resolveAfterXSeconds(2000)
  .then((ms) => { 
    console.log('promise in the first then');
    console.log(`resolved after ${ms}`);

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

Enter fullscreen mode Exit fullscreen mode

This way you can avoid the similarity of the "callback" hell, and you get exactly the same result in a more readable way.
First of all execute the code after 2 seconds, and then execute the code after 1 seconds.

PS: Btw, the name of the function should be resolveAfterXMilliseconds

Collapse
 
lampewebdev profile image
Michael "lampe" Lazarski • Edited

Hey Namir,
Thanks.

yes, this is another solution is to chain the promises. Like I have written that the given first example is just an example I have seen and should be more an anti-pattern. If you chain a lot of promises, so what you want to run your code in a sequential manner then I think that Await / Async is the even more readable solution.

time = await resolveAfterXSeconds(2000);
console.log(`resolved after ${time}`);
time = await resolveAfterXSeconds(1000);
console.log(`resolved after ${time}`);
Collapse
 
vbarzana profile image
Victor A. Barzana

Lol wanted to say the same 😉 great article btw

Collapse
 
nestedsoftware profile image
Nested Software

This code is truly parallel.

All of the code you've shown is running either concurrently (allowing other code to run while waiting for I/O) or sequentially (one after the other).

The term parallel refers to code that is running at the same time. If you have two pieces of code running in parallel, it means both are running simultaneously on two different CPUs or CPU cores.

Collapse
 
lampewebdev profile image
Michael "lampe" Lazarski

You are correct.
The example maybe I chose is not the best.

I should explain it better.

It is of course not parallel on the CPU level.
For that, you need web workers.

What I meant there is that if you call for example 2 API calls they can be called in parallel. You don't need to wait for one to finish to call another one.

Thanks for making that clear! Good job!

Have a nice day!

Collapse
 
quozzo profile image
Quozzo

Sorry but I stopped reading after I saw 4 grammatical errors in the first sentence. It should read...
"With ES8 we got another way to write code that is async in a more readable way than callbacks, it's called Async / Await."

Collapse
 
lampewebdev profile image
Michael "lampe" Lazarski

What should I now do with this comment?

How does this comment help?

And for what are you sorry?

A sorry followed by but is...

Collapse
 
paddyh4 profile image
Pradeep Chavan

Thank You for finally getting my fundamentals on async/await clear. I am Pradeep Chavan, based in India.I develop software for Tailors and Designers. I like to get in touch with you regarding the design and the working of my project, developed using php, mysql and html5, css3. My mode of communication is usually through email .please send me your email .
Thank You.
Rgds,
Pradeep Chavan.
email:pradeepdchavan@gmail.com
mob:+91 7400158620