Welcome to ciysys blog

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,

  1. query_prod() - make the database call (which v8 engine considers this as blocking).
  2. In the V8 engine - it skips waiting for the response and performs other tasks in the event loop (not query_prod()!!).
  3. query_prod() - upon database call completed, V8 will pick up the result and continue to the next line which is query_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

  1. query_prod() - make the database call (which v8 engine considers this as blocking).
  2. In the V8 engine - it skips waiting for the response.
  3. query_prod() - make the database call.
  4. In the V8 engine - it skips waiting for the response.
  5. Promise.all() - wait for completion for query_prod() and query_qoh().

As a result, the above program may complete in around 1.5 seconds.

Use cases

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.