Asynchronous Javascript (2) — More about promise and async await
By: Johnny Lai
Tags: Es6 In Depth, JavaScript, Async Functions, Javascript Promise, ES6

The Library of Trinity College

More about Promise

Promise.resolve()

Mostly we return a resolved promise object with a given value, that value can be everything, it can be another promise or thenable statement, if that value is a thenable statement, then the fulfillment status will be depends on it. For more, please refer to the MDN docs

Promise.resolve("finished").then(data=> {console.log(data)}) //output

//finished

Promise.reject()

Same as Promise.resolve() but return a rejected promise, others are same

_______________________________________________________________

Below are built-in promise functions that handle multi promise

We will re-use the promisfy examples from last post

//Promisfied setTimeout

const delay = (msg, ms) => new Promise(resolve => setTimeout(

() => resolve(msg), ms)); //Promisfied navigator.geolocation.getCurrentPosition

const geoLocation = (opts) => new Promise((resolve, reject) => navigator.geolocation.getCurrentPosition(

posData=>{resolve(posData)}, error=>{reject(error)}, opts));

Promise.race()

Kick-off multi promise at the same time and only return the result of fastest promise

Promise.race([delay("abc", 0), geoLocation()]).then(data => {console.log(data)}); //print abc, as we wait 0 second(immediately) to do that

//abc Promise.race([delay("abc", 3000), geoLocation()]).then(data => {console.log(data)}); //print location data, as it faster than print abc this time

//GeolocationPosition {coords: GeolocationCoordinates, timestamp: 1608386922668}

Promise.all()

Kick-off multi promise at the same time, if one of them fail, it will just stop and return error — all or nothing.

Promise.all([delay("abc", 3000), geoLocation()]).then(data => {console.log(data)}); //allow the location access this time

//print result of both promise

//["abc", GeolocationPosition] Promise.all([delay("abc", 3000), geoLocation()]).then(data => {console.log(data)}).catch(error => {console.log(error)}); //prints the error as we block the retrieve of location

//GeolocationPositionError {code: 1, message: "User denied Geolocation"}

Promise.allSettled()

Kick-off multi promise at the same time, and return the detail status of each promise

Promise.allSettled([delay("abc", 3000), geoLocation()]).then(data => {console.log(data)}).catch(error => {console.log(error)}); //allow the location access this time

//print detail status for both promise

//[{status: "fulfilled", value: "abc"}, {status: "fulfilled", value: GeolocationPosition}] Promise.allSettled([delay("abc", 3000), geoLocation()]).then(data => {console.log(data)}).catch(error => {console.log(error)}); //block the location access this time

//print detail status for both promise

//[{status: "fulfilled", value: "abc"}, {status: "rejected", reason: GeolocationPositionError}]

2. Async await

Besides callbacks and promise, there is another syntax in Javascript to writing asynchronous code — async await, they’re 2 reserved keywords, but we often use them together. It is similar to promise or sometimes we even say that is the syntactic sugar of promise.

Like promise, we cannot just wrap the asynchronous code with async await, we have to promisfy them first if they doesn’t return promise. For detail, please refer to the last passage

//Promisfied setTimeout

const delay = (msg, ms) => new Promise(resolve => setTimeout(

() => resolve(msg), ms)); //Promisfied navigator.geolocation.getCurrentPosition

const geoLocation = (opts) => new Promise((resolve, reject) => navigator.geolocation.getCurrentPosition(

posData=>{resolve(posData)}, error=>{reject(error)}, opts));

Consider the below code snippet in promise

delay('Some task required before retrieving location', 0)

.then(data => console.log(data))

.then(() => geoLocation())

.then(data => {console.log(data); return delay('Some task after getting the location', 0)})

.catch(err => {console.log(err); return delay('Some task after fail getting the location', 0)})

.then(data => console.log(data));

We can implement it in async await like this

async function doSomeTask() { const msgBeforeLoc = await delay('Some task required before retrieving location', 0);

console.log(msgBeforeLoc); try {

const loc = await geoLocation();

console.log(loc);

const msgAfterLoc = await delay('Some task after getting the location', 0);

console.log(msgAfterLoc);

} catch(error) {

console.log(error);

const msgAfterLoc = await delay('Some task after fail getting the location', 0);

console.log(msgAfterLoc);

}

} doSomeTask();

Let’s look at the detail

“async” keyword in front of function

We have to wrap everything inside a function with “async” in front of it. This function will return a resolved promise no matter what we’re returning now or even we do not have the return statement

That means we can append a “then” statement after and it will become a promise chain.

“await” keyword in front of statements

As mentioned above, it supports promise only, you have to promisfy the async code first if they doesn’t return promise. In the above examples, I promisfy setTimeout and navigator.geolocation.getCurrentPosition, they’re in “delay” and “geoLocation” respectively.

So, we just use them like a normal function with “await” in front of it. Inside Javascript engine, it will convert each of them into “then” statement and execute them one after each other, same as the “then” statement in promise. The result will just store in the variable we assign the await statement to it instead of the next “then” statement in the promise chain

Error handling

Async await doesn’t provide any error handling like promise does, but as shown in the above, we can use the try catch statement

How about async without await ?

Let’s try to remove those “await” inside the function.

We can still run the code, but soon we will found that those asynchronous code are not working, it seems the Javascript engine didn’t realize they are asynchronous call, it no longer “wait” them to return the result, thus we will see “Promise <pending>” in log

From Mozilla docs

“Await expressions suspend progress through an async function, yielding control and subsequently resuming progress only when an awaited promise-based asynchronous operation is either fulfilled or rejected.”

That means we have to add “await” in front of each statement invoking asynchronous code

How about await without async ?

Let’s try to remove the “async” keyword in front of function.

It will result in compilation error, we can’t use await without async

Calling multiple async await function

Consider the example below

const delay = (msg, ms) => new Promise(resolve => setTimeout(

() => resolve(msg), ms)); async function funcA() {

console.log('before a')

await delay('a', 2000).then(data=> {console.log(data)});

console.log('after a')

}

async function funcB() {

console.log('before b')

await delay('b', 2000).then(data=> {console.log(data)});

console.log('after b')

}

function run() {

funcA();

funcB();

}

run();

It prints

before a

before b

a

after a

b

after b

instead of

before a

a

after a

before b

b

after b

In fact, the caller has to be implement with async await too

async function run() {

await funcA();

await funcB();

}

run();

or we can implement with promise too

funcA().then(data => {funcB()})

3. Promise vs async await

Simpler and Less code

Async await got significant less amount of code, only wrapped inside a “async” function and added a “await” keyword in each statement invoking asynchronous code, the result will just store in the variable we assign the await statement to it. In contrast, promise chain always requires another .then() statement for next action or just handle the response

invisible then block

Consider the code below in promise

(just re-use the example from last section, but added a console log to print other task)

function trackUserHandler() {

delay('Some task required before retrieving location', 0)

.then(data => console.log(data))

.then(() => geoLocation())

.then(data => {console.log(data); return delay('Some task after getting the location', 0)})

.catch(err => {console.log(err); return delay('Some task after fail getting the location', 0)})

.then(data => console.log(data)); console.log("other task");

}

and the code below in async await

(just re-use the example from last section, but added a console log to print other task)

async function doSomeTask() {

const msgBeforeLoc = await delay('Some task required before retrieving location', 0);

console.log(msgBeforeLoc);

try {

const loc = await geoLocation();

console.log(loc);

const msgAfterLoc = await delay('Some task after getting the location', 0);

console.log(msgAfterLoc);

} catch(error) {

console.log(error);

const msgAfterLoc = await delay('Some task after fail getting the location', 0);

console.log(msgAfterLoc);

}

console.log("other task");

}

In the last section, I mentioned they produce the same output, Let’s try again the new version

The Promise example prints “other task” first

Like the callback, it executes the synchronous task first

other task

Some task required before retrieving location

GeolocationPosition {coords: GeolocationCoordinates, timestamp: 1608277437934}

Some task after getting the location

However, the async await example prints “other task” at last

because inside the async function, even we do not add await in front of the console.log(‘other task’), but Javascript engine would still wrap them with an invisible “then”, that’s why