Hello Sunil
javaScript-promises-feature-image

Learn JavaScript Promises and Async/Await

When working with asynchronous operations in JavaScript, we often hear the term Promise. Many people struggle with understanding how Promises work, so in this post I will try to explain them as simply as I can.

To understand this article better, check out my other post about JavaScript Callbacks.

Why JavaScript Promises?

Before we start explaining what a promise is and how it works, we need to take a look at the reason of its existence. In other words, we have to identify the problem that this new feature is trying to solve.

For example, when writing JavaScript, we often have to deal with tasks that rely on other tasks! Let’s say that we want to get an image, compress it, apply a filter, and save it.

Basically we need to follow four tasks:

  1. Get an image
  2. Compress it
  3. Apply filter
  4. Save it

The very first thing we need to do, is get the image that we want to edit. A getImage function can take care of this! Only once that image has been loaded successfully, we can pass that value to a resizeImage function.

When the image has been resized successfully, we want to apply a filter to the image in the applyFilter function.

After the image has been compressed and we have added a filter, we want to save the image and let the user know that everything worked correctly!

In the end, we will end up with something like this:

getImage('./image.png', (image, err) => {
    if(err) throw new Error(err)
    compressImage(image, (compressedImage, err) => {
        if(err) throw new Error (err)
        applyFilter(compressedImage, (filteredImage, err)=> {
            if(err) throw new Error(err) 
            saveImage(compressedImage, (res, err) => {
                if(err) throw new Error(err)
                console.log('Successfully saved image!')
            })
        })
    })
})

Notice anything here? Although it’sโ€ฆ fine, it’s not great. We end up with many nested callback functions that are dependent on the previous callback function.

This is often referred to as a callback hell, as we end up with tons of nested callback functions that make the code quite difficult to read!

So, in order to recap, the main problems that arise from the use of callbacks are:

  • Losing the control of our program execution (Inversion of Control)
  • Unreadable code, especially when using multiple nested callbacks

Luckily, we now got something called promises to help us out! Let’s take a look at what promises are, and how they can help us in situations like these!

What is Promise in JavaScript

In JavaScript, a promise is a good way to handle asynchronous operations. It is used to find out if the asynchronous operation is successfully completed or not.

A promise may have one of three states.

  • Pending โณ
  • Fulfilled โœ…
  • Rejected โŒ

A promise starts in a pending state. That means the process is not complete. If the operation is successful, the process ends in a fulfilled state. And, if an error occurs, the process ends in a rejected state.

JavaScript promise states

For example, when you request data from the server by using a promise, it will be in a pending state. When the data arrives successfully, it will be in a fulfilled state. If an error occurs, then it will be in a rejected state.

Promises are used to carry out asynchronous tasks like network requests. Using Promises we can write clean and understandable code. Promises were meant to avoid the nesting of callbacks.

Let’s look at an example that will help us understand Promises in a better way.

Create a JavaScript Promise

To create a promise object, we use the Promise() constructor.

let promise = new Promise(function(resolve, reject){
     //do something
});

The Promise() constructor takes a function as an argument. The function also accepts two functions resolve() and reject().

If the promise returns successfully, the resolve() function is called. And, if an error occurs, the reject() function is called.

Example of JavaScript Promise

Firstly, we use a constructor to create a Promise object:

const myPromise = new Promise();

It takes two parameters, one for success (resolve) and one for fail (reject):

const myPromise = new Promise((resolve, reject) => {  
    // condition
});

Finally, there will be a condition. If the condition is met, the Promise will be resolved, otherwise it will be rejected:

const myPromise = new Promise((resolve, reject) => {  
    let condition;  

    if(condition is met) {    
        resolve('Promise is resolved successfully.');  
    } else {    
        reject('Promise is rejected');  
    }
});

So we have created our first Promise. Kindly note that you will rarely create promise objects in practice. Instead, you will consume promises provided by libraries.

Consuming a Promise: then, catch, finally

Cool! A promise got returned with the value of the parsed data, just like we expected.

Butโ€ฆ what now? We don’t care about that entire promise object, we only care about the value of the data! Luckily, there are built-in methods to get a promise’s value. To a promise, we can attach 3 methods:

  • .then(): Gets called after a promise resolved.
  • .catch(): Gets called after a promise rejected.
  • .finally(): Always gets called, whether the promise resolved or rejected.

then( ) for resolved Promises:

The then() method is called after the Promise is resolved. Then we can decide what to do with the resolved Promise. It looks something like this ๐Ÿ‘‡

Illustration of promise chaining using .then handler

Let’s make it simpler: it’s similar to giving instructions to someone. You tell someone to ” First do this, then do that, then this other thing, then.., then.., thenโ€ฆ” and so on.

  • The first task is our original promise.
  • The rest of the tasks return our promise once one small bit of work is completed

The syntax of then() method is:

promiseObject.then(onFulfilled, onRejected);

The then() method accepts two callback functions: onFulfilled and onRejected.

The then() method calls the onFulfilled() with a value, if the promise is fulfilled or the onRejected() with an error if the promise is rejected.

Also note that both onFulfilled and onRejected arguments are optional.

For example, letโ€™s log the message to the console that we got from the Promise:

myPromise.then((message) => {  
    console.log(message);
});

catch( ) for rejected Promises:

However, the then() method is only for resolved Promises. What if the Promise fails? Then, we need to use the catch() method. But first, we need to understand the promise cycle:

An illustration of the life of a promise

Just like then(), it also returns a promise, but only when our original promise is rejected.

A small reminder here:

  • then() works when a promise is resolved
  • catch() works when a promise is rejected

Likewise we attach the then() method. We can also directly attach the catch() method right after then():

For example,

myPromise.then((message) => { 
    console.log(message);
}).catch((message) => { 
    console.log(message);
});

JavaScript finally() method:

We can also use the finally() method with promises. When we want to execute the same piece of code whether the promise is fulfilled or rejected.

For example,

const render = () => {
  //...
};

getUsers()
  .then((users) => {
    console.log(users);
    render();
  })
  .catch((error) => {
    console.log(error);
    render();
  });

As you can see, the render() function call is duplicated in both then() and catch() methods.

To remove this duplicate and execute the render() whether the promise is fulfilled or rejected, we can use the finally() method, like this:

const render = () => {
  //...
};

getUsers()
  .then((users) => {
    console.log(users);
  })
  .catch((error) => {
    console.log(error);
  })
  .finally(() => {
    render();
  });

Async functions – making promises friendly

The async and the await keyword, added in ECMAScript 2017. These features basically act as syntactic sugar on top of promises, making asynchronous code easier to write and to read afterwards.

The async Keyword:

First of all we have the async keyword, which we put in front of a function declaration to turn it into an async function.

An async function is a function that knows how to expect the possibility of the await keyword being used to invoke asynchronous code.

The keyword await makes JavaScript wait until a promise settles and returns its result.

They allow us to write promise-based code as if it were synchronous, but without blocking the main thread. They make our asynchronous code less “clever” and more readable.

Before async/await, to make a promise we wrote this:

function order(){
   return new Promise( (resolve, reject) =>{

    // Write code here
   } )
}

Now using async/await, we write one like this:

//๐Ÿ‘‡ the magical keyword
 async function order() {
    // Write code here
 }

But waitโ€ฆโ€ฆ

You need to understand first ->

  • How to use the try and catch keywords then;
  • How to use the await keyword

How to use the Try and Catch keywords

We use the try keyword to run our code while we use catch to catch our errors. It’s the same concept we saw when we looked at promises.

Let’s see a comparison. We will see a small demo of the format:

Promises in JS -> resolve or reject

We used resolve and reject in promises like this:

function kitchen(){

  return new Promise ((resolve, reject)=>{
    if(true){
       resolve("promise is fulfilled")
    }

    else{
        reject("error caught here")
    }
  })
}

kitchen()  // run the code
.then()    // next step
.then()    // next step
.catch()   // error caught here
.finally() // end of the promise [optional]

Async / Await in JS -> try, catch

When we are using async/await, we use this format:

//๐Ÿ‘‡ Magical keyword
async function kitchen(){

   try{
   // Let's create a fake problem      
      await abc;
   }

   catch(error){
      console.log("abc does not exist", error)
   }

   finally{
      console.log("Runs code anyways")
   }
}

kitchen()  // run the code

So here the keyword await makes JavaScript wait until a promise settles and returns its result.

Now hopefully you understand the difference between promises and async / await.

How to Handle Multiple Promises

Apart from the handler methods (then(), catch(), and finally()), there are six static methods available in the Promise API. The first four methods accept an array of promises and run them in parallel.

  1. Promise.all
  2. Promise.any
  3. Promise.allSettled
  4. Promise.race
  5. Promise.resolve
  6. Promise.reject

Let’s go through each one.

The Promise.all() method

Promise.all([promises]) accepts a collection (for example, an array) of promises as an argument and executes them in parallel.

This method waits for all the promises to resolve and returns the array of promise results. If any of the promises reject or execute to fail due to an error, all other promise results will be ignored.

The Promise.any() method

Promise.any([promises]) – Similar to the all() method, .any() also accepts an array of promises to execute them in parallel. This method doesn’t wait for all the promises to resolve. It is done when any one of the promises is settled.

The Promise.allSettled() method

Promise.allSettled([promises]) – This method waits for all promises to settle(resolve/reject) and returns their results as an array of objects.

The results will contain a state (fulfilled/rejected) and value, if fulfilled. In case of rejected status, it will return a reason for the error.

The Promise.race() method

Promise.race([promises]) โ€“ It waits for the first (quickest) promise to settle, and returns the result/error accordingly.

The Promise.resolve/reject methods

Promise.resolve(value) โ€“ It resolves a promise with the value passed to it. It is the same as the following:

let promise = new Promise(resolve => resolve(value));

Promise.reject(error) โ€“ It rejects a promise with the error passed to it. It is the same as the following:

let promise = new Promise((resolve, reject) => reject(error));

Conclusion

If you are here and have read through most of the lines above, congratulations! You should now have a better grip of JavaScript Promises.

In short, a Promise is an object that once called upon, will eventually resolve or reject and return a response based on some criteria that is specified within the Promise object.

We hope this post will help you in your journey. Keep learning!

Thanks for readingโ€ฆ๐Ÿ˜Š

Resource

How useful was this post?

Click on a star to rate it!

Average rating 0 / 5. Vote count: 0

No votes so far! Be the first to rate this post.

We are sorry that this post was not useful for you!

Let us improve this post!

Tell us how we can improve this post?

Similar articles you may like

Sunil Pradhan

Hi there ๐Ÿ‘‹ I am a front-end developer passionate about cutting-edge, semantic, pixel-perfect design. Writing helps me to understand things better.

Add comment

Stay Updated

Want to be notified when our article is published? Enter your email address below to be the first to know.

Sunil Pradhan

Hi there ๐Ÿ‘‹ I am a front-end developer passionate about cutting-edge, semantic, pixel-perfect design. Writing helps me to understand things better.