DEV Community

Cover image for The only guide you'll ever need to understand Promises and Async await
Rishav Jadon
Rishav Jadon

Posted on • Updated on

The only guide you'll ever need to understand Promises and Async await

When it comes to **Promises* and Async await which are closely related concepts in Javascript, people are always confused even after watching tutorials or reading numerous articles about them. Well, worry not because I assure you if you read this article thoroughly you will have a better understanding of what they are and how they work.*

The Evolution of Asynchronous programming

Untitled-2021-10-12-1403.png

Callback functions --> Promises --> Async await

Don't worry, we will understand what this evolution is all about and why it took place, further in the article.

Javascript is synchronous, or is it?

Javascript is a synchronous language, meaning that every statement is run line by line in a sequential manner, and only when a line of code is done being executed is when the next line will run.

Now, what if something takes a long time to execute than usual, and it blocks the whole program? This is bad, we don't want to slow down our program just because a single line of code (which might be an API call to get an image resource) is blocking our entire script.
That is why we needed asynchronous code, which does not block the code after it, and can run in the background. There are a lot of ways we can do asynchronous programming in JavaScript: callback functions, Promises and Async await.

Callback functions

Callback functions are basically functions passed to functions as arguments to be executed later, and they can be synchronous or asynchronous. One most popular example of this is the s*etTimeout* function.

setTimeout(function() {
  console.log('Hello World!');
}, 500);
Enter fullscreen mode Exit fullscreen mode

This code does a console.log after 500 milliseconds and runs asynchronously.
A synchronous callback function might look like this:

let numbers = [1, 2, 4, 7, 3, 5, 6]
numbers.sort((a, b) => a - b)
console.log(numbers); // [ 1, 2, 3, 4, 5, 6, 7 ]
Enter fullscreen mode Exit fullscreen mode

The function sort() has an anonymous arrow function that works as a callback to sort the array, and we print it on the console.
Notice that the arrow function runs before the console and therefore we get the sorted array, but if you would have sorted the same array with a setTimeout then the console will print unsorted array:

let numbers = [2, 1, 4, 7, 3, 5, 6]
// numbers.sort((a, b) => a - b)
setTimeout((a,b) => {
    numbers.sort((a,b) => a-b)
    // console.log(numbers)
}, 0)
console.log(numbers); // [2, 1, 4, 7, 3, 5, 6]
Enter fullscreen mode Exit fullscreen mode

This is because asynchronous code runs after all synchronous code is finished executing, that is why even though the setTimeout had 0 wait time, it still ran after the console.log, even though console.log comes later in code.

Callbacks are good for asynchronous programming as long as you have very few async actions, but if you want to run more functions asynchronously, then callbacks become an ugly mess!

Imagine if you have a number of requests and you want to run them in callback functions. The code might look like this:

const url1 = 'https://fakeapi.com/1/'
const url2 = 'https://fakeapi.com/2/'
const url3 = 'https://fakeapi.com/3/'

function callback(url,msg){
// calls api here
console.log(msg)
}
setTimeout(() => {
    callback('first message')
    setTimeout( () => {
        callback('second message')
        setTimeout( () => {
            callback('third message')
        },0)
    }, 0)
},0) 
// first message
// second message
// third message
Enter fullscreen mode Exit fullscreen mode

These nested callbacks create what is infamously called the Callback Hell, and this type of code can become unmaintainable very quickly.
To solve this, Promises were introduced in ES6( Ecmascript 2015).

All these Promises you do

Promises in JS are like promises in real life, it either is resolved or never completed.
There are certain things you need to know to understand Promises better. A Promise can be in one of the three states:

  • resolved
  • rejected
  • pending

Promise objects can be made by the Promise() constructor, which takes in an executor function, which in turn takes two functions as parameters: a resolve function and a reject function.
This is how you make a new promise:

const aPromise = new Promise( (resolve, reject) => {
  resolve("promise resolved")
})

aPromise.then(( value) => {
  console.log(value)
})
.catch( (error) => {
  console.log(error)
})
Enter fullscreen mode Exit fullscreen mode

This "executor" function terminates with the invocation of resolve or reject function depending on the value of the Promise, meaning that the executor function runs some code, and calls resolve function if the Promise gets resolved or reject function if it throws an error.

After your promise is resolved or rejected you can chain it with the methods .then() and .catch() respectively. These methods themselves return Promises.
You can also chain multiple .then() methods to modify the value that comes back from a resolved Promise:

aPromise.then(( value) => {
  return value = 'changed value'
})
.then((value) => {
  console.log(value)
})
Enter fullscreen mode Exit fullscreen mode

promise.png
We usually use these methods while making an api call.
Let's use the fetch API to fetch list of Pokemon from the PokeAPI. The fetch(). function returns a Promise so we can attach our .then() and .catch() methods:

const url = 'https://pokeapi.co/api/v2/pokemon?limit=100&offset=200'
fetch(url)
  .then((response) => {
    return response.json()
  })
  .then((data) => {
   console.log(data)
  })
Enter fullscreen mode Exit fullscreen mode

This will give us a list of 100 Pokemon with other properties. Promises did make our lives easier but our code still looks like it's bulky with all these chaining methods. Async await made this much more cleaner and allowed us to write asynchronous code that looked very much like our good old synchronous code.

Async await

Remember that Async await is kinda like Classes in Javascript, it might look like we are writing entirely different code, but under the hood, we are still using Promises. It's just that the syntax and readability got better.

Async await is used in place of .then() and .catch() when working with Promises. We need to assign a function ASYNC and then inside of it we can have the await keyword before the asynchronous actions we'd like to perform ( meaning the code we think might take longer time). Like this:

const loadData = async () {
    const url = 'https://jsonplaceholder.com/todos/1/'
    const res = await fetch(url)
    const data = res.json()
    console.log(data)
}
loadData()
Enter fullscreen mode Exit fullscreen mode

Can you see the difference between this and Promises? This is so much cleaner and doesn't even look like we are doing something asynchronously. Just define an async function and add await before anything you'd want to run asynchronously.

Using async on a function, with await inside on any operation( like an API call) will say to JS : "Hey man just don't return this function until this thing with the await is done, after that do whatever you gotta do"

Things to remember about Async await:

  • always assign the function you want to have asynchronous code in as an async function.
  • you can only use await inside of an async function.
  • An async function returns a Promise, so you can chain multiple async functions.

try-catch block

Just like the .catch() block we used in Promises, we use try-catch for error handling in Async await. This makes the code much cleaner and more organized.

const loadData = async () {
try{
    const url = 'https://jsonplaceholder.com/todos/1/'
    const res = await fetch(url)
    const data = await res.json()
    console.log(data)
  }
catch(err){
   console.error(err)
 }
}
Enter fullscreen mode Exit fullscreen mode

Async functions also return a promise. So get this, a fetch or an Axios request returns a promise, an async function also returns a promise. We can either do a .then() chaining or another async await on a Promise.

Promise.all()

If you need to make multiple requests to an API, and you go by the approach of async await, it will take the sum of time it takes for all requests to return data. So let's say each request takes 1 sec, and for 3 requests, we will have to wait 3 secs if we just await on every single request like this:

/* Wrong approach */
const loadData = async() {
  try(){
    const url = 'https://jsonplaceholder.com/todos/1/'
    const url2 = 'https://jsonplaceholder.com/todos/2/'
    const url3 = 'https://jsonplaceholder.com/todos/3/'

    const res = await fetch(url)
    const res2 = await fetch(url2)
    const res3 = await fetch(url3)
    const data = await res.json()
    const data2 = await res2.json()
    const data3 = await res3.json()
    return  [data, data2, data3]
   }
   catch(err){
    console.error(err)
   }
}
Enter fullscreen mode Exit fullscreen mode

With Promise.all(), each request runs parallel to each other, resulting in a much quicker response.

/* Right approach */
const loadData = async() {
  try(){
    const url = 'https://jsonplaceholder.com/todos/1/'
    const url2 = 'https://jsonplaceholder.com/todos/2/'
    const url3 = 'https://jsonplaceholder.com/todos/3/'
    const results = await Promise.all([
      fetch(url),
      fetch(url2),
      fetch(url3)
     ])
    const dataPromises = results.map( res => res.json())
    const finalData = await Promise.all(dataPromises)
    return finalData
   }
   catch(err){
    console.error(err)
   }
}

( async() => {
const data = await loadData()
console.log(data)
})()
Enter fullscreen mode Exit fullscreen mode

That's all. We learned a lot of things, what is asynchronous programming, how do callback functions work, about Promises and Async await.

For further reading:

Thanks for reading!

If you like my work you can support me on https://www.buymeacoffee.com/rishavjadon

Top comments (4)

Collapse
 
cherrydt profile image
David Trapp • Edited

You have a small mistake:

  .then((response) => {
    response.json()
  })
Enter fullscreen mode Exit fullscreen mode

...this won't work because it won't return the response.json() promise.

You need either...

  .then(response => response.json()) // without { }
Enter fullscreen mode Exit fullscreen mode

...or...

  .then(response => {
    return response.json()
  }
Enter fullscreen mode Exit fullscreen mode

Additionally, I find this confusing:

aPromise.then(( value) => {
  return value = 'changed value'
})
.then((value) => {
  console.log(value)
})
Enter fullscreen mode Exit fullscreen mode

There is no point in modifying the local variable value here as it's not used anymore anyway after this return. The following would work too:

aPromise.then((value) => {
  return 'changed value'
})
.then((value) => {
  console.log(value)
})
Enter fullscreen mode Exit fullscreen mode

...or alternatively aPromise.then(value => 'changed value').


One more mistake here - missing await:

const loadData = async () {
    const url = 'https://jsonplaceholder.com/todos/1/'
    const res = await fetch(url)
    const data = res.json() // << should have `await`
    console.log(data)
}
loadData()
Enter fullscreen mode Exit fullscreen mode

Also, this invites unhandled rejections. You should point out that you always need to append a .catch if you are calling an async function without await (and without storing/forwarding the returned promise)! Like this:

loadData().catch(e => console.error('Loading data failed:', e))
Enter fullscreen mode Exit fullscreen mode

In my book, anything that looks like this is automatically a bug.

async function whatever () { /* ... */ }

whatever() // BUG

await whatever() // okay
whatever().catch(e => { /* proper handling here */ }) // okay
return whatever() // okay if the caller handles rejected promises
const myPromise = whatever() // okay if myPromise gets a .catch somewhere else
Enter fullscreen mode Exit fullscreen mode

Note that a try/catch inside of the async function is not sufficient, because code inside the catch clause may itself throw an exception.

Collapse
 
rjitsu profile image
Rishav Jadon

Yeah you're right. Thanks for pointing it out

Collapse
 
lowlifearcade profile image
Sonny Brown

Nice article man. Thank you.

Collapse
 
rjitsu profile image
Rishav Jadon

Glad you like it!