Promise and Async/Await Javascript
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.
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
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.
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.