Promise and Async/Await Javascript

13 Min. Read
May 20, 2021

JavaScript is a single-threaded programming language with a synchronous execution model that processes one statement after another, It can only process one statement at a time. But, there are times when it has to execute statement that takes an indeterminate amount of time to complete, like requesting data from API, read/write data in disk, etc. Since being single-threaded and synchronous, these actions will cause the browser to halt the execution of any other statement and the website appears to be stuck. This is called blocking.

Stuck and not responding website is not fun, to prevent this blocking behavior, javascript engine takes help from its environment (browser or Node.js), the environment has many Web APIs that can handle these types of actions Asynchronously, meaning parallel to the execution of another statement. This allows users to continue using the website smoothly while the asynchronous operations are being processed. JavaScript engine passes these statement to Web APIs and javascript engine continues to execute another statement. When the Web APIs finish execution, it informs the javascript engine and gives access to the result.

Promise is one of the way of handling asynchronous codes in javascript, lets go learn more about it.

lets go

Promise

Promise in JavaScript was introduced, in ES6 2015, to handle asynchronous code in more manageable way. Promise in javascript is an object that may produce a single value sometime in the future: either a resolved value or a reason for rejection. It is similar to promise in real life, It is a commitment that we give to complete certain tasks. For example:

Asim is attending a JS conference, To enter the venue there is a long line of people, at the entrance, there is a ticket checker. Only the people with the ticket can enter the gate. Async is at … oh wait, I mean Asim, Asim is at the line, and the ticket checker is processing one person at a time, checking the ticket and accessing the venue. Now, its time to process Asim, but Oh no!, Asim forgot his ticket in his car, Now what Asim does is, Asim tells the ticket checker that he doesn’t have a ticket right now, he PROMISES that he has the ticket and he will call his friend Api to get it from his car. The ticket checker then puts Asim aside and continues to process the next person in line. Parallelly Asim calls Api to get the ticket from his car. Asim waits and when Api returns and gives Asim the ticket, Asim informs the Ticket checker that he has got the required ticket. Now when the ticket checker completes current processing, he processes Asim and lets him into the conference.

Creating a Promise

Promise is a Javascript Object, We Initialize Promise using new Promise syntax, Like below:

1
2
3
4
5
6
7
8
9
10
11
    // Initialize a promise
    const promise = new Promise((resolve, reject) => {
        // do something awesome
        // after it completes
        if(isSuccessful){
            resolve("Success");
        }
        else{
            reject("Failure");
        }
    })

We can see that we pass a function to Promise, the function has two parameters resolve and reject. resolve handles success and reject handles failure of the operation.
Now, you may be curious to know what does the promise returns. let’s create a simple promise, and see what happens, try it in your browser console.

1
2
3
4
5
6
7
8
    // Initialize the promise in console.
    let i_will_show_you_my_ticket = new Promise((resolve, reject) => {
        // resolve("Here is my Ticket");
        // reject("Oh no!, I must have left it at my home");
    })

    // inspect the promise in console.
    i_will_show_you_my_ticket;

Lets examine the output:

1
2
3
4
5
6
    i_will_show_you_my_ticket;
    // Output
    Promise {<pending>}
    __proto__: Promise
    [[PromiseState]]: "pending"
    [[PromiseResult]]: undefined

We can see Promise has PromiseState and PromiseResult, our Promise is in a pending state because it has not reached resolved or reject function. And we can see PromiseResult is undefined.

As you see, we have the resolve and reject functions commented out, try uncommenting one and the other, and see what we get. And we will talk about it more below.

States of Promise

Promise has three possible states,

  • pending: initial state before resolve or reject, its result is undefined
  • fulfilled: state after resolved, operation is successful, its result is value
  • rejected: state after reject, operation is failed, its result is error object
After the promise gets fulfilled or rejected, the promise is completed.

Using Promise

We have learned about creating a promise, now we are going to learn about how to access the value of promise and handle rejection. Promise has three methods to handle its result and they are then, catch and finally.

.then()

Promise has athen method that will be called after promise reaches resolve in the code. then will receive promise’s value as a parameter. A promise can have multiple then methods chained together to handle complex interdependent asynchronous tasks.

1
2
3
4
5
6
7
8
9
10
11
    promise
    .then(response_from_promise => {
        ...do_something
    })
    .then(response_from_then1 => {
        ...do_something_more
    })
    .then(response_from_then2 => {
        ...do_something_more
    })
    ...

This chaining feature makes promises appear more synchronous than nesting callbacks to handle asynchronous code. This feature makes the promise more readable and maintainable.

.catch()

catch method is present to handle rejects of the promise, Not all Promises get fulfilled, some are broken and rejected. We need to handle rejects of promise. .catch is called when the Promise callback function calls reject function, the reject function usually has error messages in its parameter, catch can be used to display the error messages.

1
2
3
4
5
6
7
8
9
10
11
12
    const promise = new Promise((resolve, reject) => {
        reject("Failed to fetch data!");
    })

    promise
    .then(response_from_promise => { // this is skipped 
        ...do_something
    })
    .catch( error => {
        console.error(error);
    })

.finally()

finally method is called when the promise is settled, which means either resolve or reject is called. We use finally when we don’t create about the reason for a rejection or fulfilled value. We use it when we want to do something after the promise is settled. This helps to avoid duplicating code in both the promise’s then() and catch() handlers.

1
2
3
4
5
6
7
8
9
10
    promise
    .then(response_from_promise => {
        ...handle success
    })
    .catch(error => {
        ...handle error
    })
    .finally(() => {
        ...handle promise settled code.
    })

Let’s try it practically, To bring asynchronous touch in our code, let us use setTimeout function, setTimeout is handled by asynchronous Web API - setTimeout(callback, delay) first parameter is a callback function and second parameter is delay in millisecond, asynchronous Web API stores callback temporarily and waits for delay to complete and after the delay completes callback function is called.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    let i_will_show_you_my_ticket = new Promise((resolve, reject) => {
        setTimeout(()=>{
            resolve("Here is my Ticket 🎫");
            // reject("Cant find the Ticket")
        }, 5000); // 5 seconds
    })

    i_will_show_you_my_ticket.then( response => {
        console.log(response);
        // go inside the conference.
    })
    .catch( error => {
        console.log(error);
        console.log("I dont have the ticket");
    })
    .finally(()=>{
        console.log("Thank you Ticket Checker for your patience")
    })

In above example, we created a promise called i_will_show_you_my_ticket. Inside the promise we have setTimeout which calls resolve after 5000ms.
At line 8, we are listening to the promise with .then. After 5 seconds the promise gets resolved and the value of resolve is passed to then methods’ parameter (in our case response). we are consoling response at line 9, so the output will be Here is my Ticket after 5 seconds, and after then , finally is called.

1
2
3
   //Output
    Here is my Ticket
    Thank you Ticket Checker for your patience

Now, Try it in your browser console by commenting line 3 ( resolve ) and uncommenting line 4 ( reject ). Guess the output and see the result in the console, you have 5000 milli-seconds to guess .

Promise.all

Promise executes parallely, thats great but what if we want insure the order of return is same as order of call. Like calling promise1, promise2 and getting promise1result and then promise2result. This case introduces similar problem like callback hell.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const resolveThisAfter = (ms) => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(ms);
    }, ms);
  });
}

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


//Output
resolved after 2000
resolved after 1000

here, function with 2000 is called and once resolved, resolveThisAfter(1000) is called and ms of first function is passed to its .then handler. And after second function resolves it is handled by its .then method. This is not very readable. Here is where Promise.all comes in.

1
2
3
4
5
6
// we are using resolveThisAfter from above code... 

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

Promise.all() takes an array of promises and creates a single Promise which is fulfilled when all the promises it depends on are also fulfilled. All the promises in the Promise Array is executed at the same time, but its .then method is called only after all the promises are fulfilled. The .then method receives an array with resolved promises. The array has resolved values stored in same order as the promises were passed to the Promise.all() function.

We will need all of this information to better understand Async / Await!
Now let’s learn abot Async / Await.

finnaly

Async / Await

Async / Await is a morden syntax to handle multiple promises in synchronous fashion. It was introduced in ES2017, async/awit is build on promises. Async / Await makes code look synchronous, but it’s asynchronous and non-blocking behind the scenes.

We can create an Async function by adding the async keyword before a function. Like this:

1
2
3
4
5
6
7
8
9
10
11
12
// Creating async function.

async function myAsyncFunction () {
    // some asynchronous code
    return {};
}

// ES6 Arrow Function declaration
const myAsyncFunction = async() => {
    // some asynchronous code
    return {};
}

async function are different from traditional function. async function returns Promise with [[PromiseStatus]] and [[PromiseValue]] instead of a return value. Try it in your browser console. Copy one of the above function declarations and paste into in the console. Then execute console.log(myAsyncFunction())

1
2
3
4
// Output
__proto__: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: Object

By adding async before function declaration we made the function asynchronous, meaning when the function gets called, it returns a Promise and normal code continues to execute as usual. In the async function, we have an await operator. await operator is placed before a promise and it blocks the execution of the async function until the promise it awaits gets resolved or rejected. The async function is asynchronous, but the code inside the async function behaves as synchronous.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const resolveThisAfter = (ms) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(ms);
    }, ms);
  });
}

async function hello_world(){
    console.log('hello');
    let res= await resolveThisAfter(2000);
    console.log(res);
    console.log('world');
    return res;
}

hello_world().then(response => console.log("Resolved after:" + response));


// Output
hello
2000
world
Resolved after:2000

Here, hello_world is the async function, when it is called, it executes its first line and, then when it gets await, the await keyword blocks the flow of the async function until its promise gets resolved. After 2000ms,the promise is resolved and res is assigned 2000 and the flow of the function continues, and res is returned as PromiseValue which is then handled by the .then method.

Handling Rejections and Error with Async/Await

When a promise inside the async function is rejected, the returning promise of the async function is also rejected with an error message. The returning promise is rejected also when there is any runtime error inside the async function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const rejectThisAfter = (ms) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
        reject("ERROR");
        }, ms);
    });
}

async function hello_world(){
    console.log('hello');
    let res= await rejectThisAfter(1000);
    console.log(res);
    console.log('world');
    return res;
}

hello_world().then(response => console.log("Resolved after:" + response)).catch(err => console.error("We have a ", err));

// Output
hello
We have a  ERROR

The promise returning by rejectThisAfter is rejected which crashes the thread in which the async function is running and it is then handled by the .catch handler of the hello_world() promise.

To handle promise rejection, we should use the try/catch method inside the async function.

A Common pattern nowadays is to completely handle promise inside of the async function and get rid of outer promise handlers.

Let’s do a practical implementation of handling asynchronous code with async / await.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const getUserById = async (id) => {
    try{
        const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`);
        let result = await response.json();
        console.log(result);
    }
    catch(err) {
        console.log("ERROR:", err);
    }
}

getUserById(1);

// Output
{
    body: "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto",
    id: 1,
    title: "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
    userId: 1
}

Here, we have the getUserById async function, the function takes id as its parameter and the function has a try/catch handler. Inside the try block, we are fetching data from an API, Since fetch returns a Promise, we add the await operator before fetch to block the execution of the async function until the promise is settled. .json() method also returns a promise which resolves to javascript object. ( since it also returns a promise, we add await in front of it. )

If there is any problem while fetching the API (like file not found, incorrect URL, cors, etc), or any run time error, the catch method is invoked and the error is handled there.

Summary

JavaScript is single-threaded and it runs synchronously line by line. But, it can have operations that are time-consuming and uncertain, these operations if handled synchronously causes blocking in the website, which causes an extremely bad user experience. So, we handle them Asynchronously, parallel to other execution, non-blocking way.

Promise is used for handling asynchronous functions in a more manageable way than previously used callback method, Promise is an object that may produce a result in the future, Promise commits that it will provide the value in the future which allows the js engine to continue with executing other operations. Asynchronous operations are done inside Promise’s callback function and, according to the result of the operation, Promise is either resolved or rejected. Promise has its own methods like .then(), .catch() and, .finally() to handle the result of Promise.

Promise pattern to handle asynchronous code is better than callback pattern but, it had its own complications, So, in ES2017 async/await was introduced, async/await is built on Promise and it has made handling asynchronous code very easy to maintain. An async function is a special function that returns a Promise, the async function is executed asynchronously, and it has await operator inside it, which can block the execution of an async function until the awaited promise gets resolved. Async/Await has brought synchronous feeling in asynchronous code.