Async/await vs Promise and its side effects
Published on: 14th Mar 2022
Overview
By default, JavaScript is executing code synchronously. This is where it starts. X years later, they invented Promise
to make the code run asynchronously. Now, JavaScript allows chaining the processes with callback. And then they realized that it is difficult to read the code, they added an async
keyword where async
will halt at the line, waiting for completion and then move on to the next line.
Let's write some code
There are two ways of declaring a Promise based function: returns a Promise
or using async
keyword. Both methods serve the same purpose: the code will run asynchronously.
function query_prod(id, style) {
return new Promise(function (resolve, reject) {
let s = style + ':query_prod():prod_id=' + id;
console.log(s + '-> start');
// delay responding to the caller to simulate slow
// server responds.
setTimeout(() => {
console.log(s + '-> done');
resolve(s);
}, 1500);
});
}
async function query_qoh(id, style) {
let s = style + ':query_qoh():qoh for=' + id;
console.log(s + '-> start');
console.log(s + '-> done');
return s;
}
With Promise style, you may call the above functions like this,
query_prod(123, 'Promise')
.then((prod) => {
query_qoh(123, 'Promise')
then((qoh) => {
// do other processes.
});
});
Or you may call the above functions using await
keyword like this,
(async function(){
let prod = await query_prod(123, 'await');
let qoh = await query_qoh(123, 'await');
})();
Both the Promise
and async/await
serve the same purpose: run the program asynchronously. In V8 engine (who execute the JavaScript code in Node.js and Chrome browser), it will do the following,
query_prod()
- make the database call (which v8 engine considers this as blocking).- In the V8 engine - it skips waiting for the response and performs other tasks in the event loop (not
query_prod()
!!). query_prod()
- upon database call completed, V8 will pick up the result and continue to the next line which isquery_qoh()
.
This is how V8 avoids the program blocking.
The result for using Promise
,
Promise:query_prod():prod_id=789-> start <=== Fired and
Promise:query_prod():prod_id=789-> done <=== wait for result
Promise:query_qoh():qoh for=789-> start <=== then, fire next and wait for the result.
Promise:query_qoh():qoh for=789-> done
The result for using await
,
await:query_prod():prod_id=789-> start <=== Fired and
await:query_prod():prod_id=789-> done <=== wait for result
await:query_qoh():qoh for=789-> start <=== then, fire next and wait for the result.
await:query_qoh():qoh for=789-> done
There is one catch in the above code - the query_qoh()
will be started after query_prod()
completion. Let's say query_prod()
takes about 1.5 seconds to complete, query_qoh()
takes about 0.5 seconds to complete and the total execution time will be around 2 seconds.
What if you want to reduce the total execution time? Can it be done? The answer is yes. See the following example,
let query_prod_task = query_prod(123, 'Promise');
let query_qoh_task = query_qoh(123, 'Promise');
let task_list = [
query_prod_task,
query_qoh_task
];
// wait for all tasks to be completed
Promise.all(task_list)
.then(function (l2) {
// do other processes.
});
Or
query_prod(123, 'Promise')
.then((prod) => {
// do other processes.
});
query_qoh(123, 'Promise')
then((qoh) => {
// do other processes.
});
The result of the above code looks like this,
Promise:query_prod():prod_id=123-> start <=== Fired immediately and not waiting for completion.
Promise:query_qoh():qoh for=123-> start <=== Then, fire the next function.
Promise:query_qoh():qoh for=123-> done
continue the process
Promise:query_prod():prod_id=123-> done
Basically, here is how V8 executing the code
query_prod()
- make the database call (which v8 engine considers this as blocking).- In the V8 engine - it skips waiting for the response.
query_prod()
- make the database call.- In the V8 engine - it skips waiting for the response.
Promise.all()
- wait for completion forquery_prod()
andquery_qoh()
.
As a result, the above program may complete in around 1.5 seconds.
Use cases
- If you have to process a large amount of data, you must use
Promise()
orasync/await
to avoid blocking the V8 event loop. - If the process must be done one after another without blocking the V8 event loop, you may use
await
. - If you are querying the database server or web server, you may use
Promise.all()
and multiple lines ofPromise.then()
to reduce the total execution time. - If you want to process large number of data and you don't want to flood your database server or web server, you will have to do this:
let opt = {
idx:0,
data: []
};
for (var i = 0; i < 100_000; i++) {
opt.data.push('some data:' + i.toString());
}
function save_data() {
let data = opt.data[opt.idx];
// save the data into the database..
// next item
opt.idx++;
// schedule the saving if not exceeded the array size.
if (opt.idx < opt.data.length) {
setTimeout(function() {
save_data();
},
// delay the saving for 10ms to avoid flooding the database server and event loop.
10);
}
}
save_data();
Conclusion
Async/await
is not a solution for everything and the Promise()
is still having a reason to exist. The most important is that you have to determine what you want to achieve before choosing the right strategy for your program.
Related posts
Jump to #JAVASCRIPT blog
Author
Lau Hon Wan, software developer.