Thursday, August 27, 2020

async and await in a nutshell

The async and await keywords introduced in Ecmascript 2017 are significant improvements on the way asynchronous functions in Javascript have been called in the past, and are now supported in all major browsers, and in Node.js. Unfortunately, most explanations on the Web are marred by unnecessary detail. For the benefit of other confused readers I will try to explain the underlying concepts in the simplest possible terms.

Javascript functions come in two basic flavours: asynchronous and synchronous. Asynchronous functions execute when the time is right, whereas synchronous functions execute straightaway. Asynchronous functions give Javascript its power by maximising the usage of the CPU instead of blocking while network and other in/out operations complete.

The async and await keywords simplify the calling of asynchronous functions, which can now be declared with the async keyword. This can be applied to a simple function, a method in a class or a static class method:

async function foo(){ return 1;}
class bar { async foo(){ return 1;} }
class bar { static async foo(){ return 1;} }

When called, these functions or methods will yield the CPU to other pending functions rather than execute immediately. The await keyword lets you treat asynchronous functions as if they were synchronous. That is, you don't have to obscure the clarity of your program with callbacks, explicit promises or then-clauses to get things done. (Hallelujah!) Here's a simple example that uses async/await in a loop:

async function bar() {
   return 2;
async function foo(){
    let arr = Array();
    let total = 0;
    for ( let i=0;i<arr.length;i++ ) {
        if ( i == 1 )
            total += await bar();
            total += arr[i];

This adds up a list of numbers: 1, 7 and 3, whose total should be 11. But when it tries to add the second element of the array it gets it instead from the asynchronous bar() function, which returns 2 rather than 7. The program can be written as if it were synchronous because that call to bar() is prefixed by the await keyword. In effect, the code pauses at that point until bar() completes, before executing the next step in the loop. So the answer is 6, not 11.

However, you can only use await inside a function declared with async. Also, if you call an async function inside a synchronous function, or at the top level, the next step will execute immediately and not wait for the async function to complete. In the above example the final call to foo() is from the synchronous top-level, but it doesn't matter because this is the last statement in the program.

And finally, many functions defined in frameworks are already asynchronous. You can also call these using the await keyword. For example in jQuery you can use await with $.get, as long as you call it within a function declared with async:

let data = await $.get(url);
// do something with data...

So you don't have to worry about callbacks, and that's cool.