Bootcamp
Search…
0.8.3: Async/Await

Introduction

Async and await, commonly abbreviated as async/await, is an ES6 syntax built upon Promises that enables writing asynchronous JavaScript in a synchronous manner without callbacks. The async keyword specifies that a given function is asynchronous and returns a Promise, and the await keyword will wait on a given Promise to resolve before proceeding to the next line. async and await keywords must be used together; it is not meaningful to use async without await, and it is invalid syntax to use await without async.

Normal Asynchronous Function Call: setTimeout

The following script does not use async and await keywords, or even Promises. It calls the function doStuff, which calls setDelay, which invokes an asynchronous timeout callback. Each of the console log statements runs in synchronous code order, with the exception of the setTimeout callback, which runs after the timeout duration.

Script File

const randomNumber = () => Math.floor(Math.random() * 1000);
​
const setDelay = (delay) => {
console.log(`delaying ${delay}`);
setTimeout(() => {
console.log('done with timeout');
}, delay);
};
​
const doStuff = () => {
console.log('setting timeout');
setDelay(randomNumber());
console.log('after call settimeout');
};
​
console.log('about to call do stuff');
doStuff();
console.log('finished call do stuff');

Output

about to call do stuff
setting timeout
delaying 852
after call settimeout
finished call do stuff
done with timeout

Asynchronous Function Call with Promise and Async/Await

The following code is slightly different in that there is an await keyword on line 16, which causes the console log on line 17 to wait for setDelay to resolve before running. Meanwhile, because doStuff is an async function, doStuff will return with a Promise before doStuff runs to completion. Async/await gives us simpler syntax to control which parts of our code should run asynchronously and synchronously.

Script File

const randomNumber = () => Math.floor(Math.random() * 1000);
​
const setDelay = (delay) => {
console.log(`delaying ${delay}`);
​
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('done with timeout');
resolve(delay);
}, delay);
});
};
​
const doStuff = async () => {
console.log('about to set timeout');
await setDelay(randomNumber());
console.log('after call setTimeout');
};
​
console.log('about to call do stuff');
doStuff();
console.log('finished call do stuff');

Output

about to call do stuff
about to set timeout
delaying 934
finished call do stuff
done with timeout
after call setTimeout

Async/Await with pg Library

So far we have been using .then syntax with pg library methods. Let's see how we could convert our .thens to awaits. In the following code, the return value of await pool.query(...); on line 4 is the resolved value of the Promise returned by pool.query. More info on using pg with async and await here.

Async/Await Example

app.get('/users/:id', async (request, response) => {
const { id } = request.params;
console.log('making query');
const { rows } = await pool.query('SELECT * FROM users WHERE id = $1', [id]);
console.log('done with query');
response.send(rows);
});

.then Example

app.get('/users/:id', (request, response) => {
const { id } = request.params;
console.log('making query');
pool.query('SELECT * FROM users WHERE id = $1', [id]).then((result) => {
const { rows } = result;
console.log('done with query');
response.send(rows);
};
});

Async/Await with Try/Catch

From this Promise .catch example, how do we keep the error handling of .catch, and still convert this code from Promises to async/await? We can go back to the original syntax of the try block.

.then, .catch Syntax

let category = 'vegan';
​
client
.query('SELECT * from categories WHERE name= $1', [category])
.then((result) => {
let categoryId = result.rows[0].id;
return client.query('SELECT * FROM recipes WHERE category_id = $1', [
categoryId,
]);
})
.then((result) => {
let recipes = result.rows;
​
// ...
})
.catch((error) => {
// in Express.js we might respond 400 or 500.
console.log(error);
});

Async/Await, Try/Catch Syntax

const getData = async (category) => {
try {
const categories = await client.query(
'SELECT * from categories WHERE name= $1',
[category]
);
​
let categoryId = categories[0].id;
const recipes = await client.query(
'SELECT * FROM recipes WHERE category_id = $1',
[categoryId]
);
​
// ...
​
// catch errors thrown by client.query
} catch (error) {
console.log(error);
}
};
​
getData('vegan');

Async/Await Helps Control Where to Pause and Where to Continue

await syntax appears to cause our program to pause on a given line, e.g. line 13. However, note that the async function doStuff returns before it has completed, allowing lines 19-21 to run before doStuff on line 18 is complete.

Script File

const setDelay = (delay) => {
console.log(`delaying ${delay}`);
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('done with timeout');
resolve(delay);
}, delay);
});
};
​
const doStuff = async (delay) => {
console.log(`about to set timeout with delay: ${delay}`);
await setDelay(delay);
console.log(`after call setTimeout with delay: ${delay}`);
};
​
console.log('about to call do stuff, wait a second');
doStuff(1000);
console.log('about to call do stuff 2nd time, wait .7 sec');
doStuff(700);
console.log('finished call do stuff');

Output

about to call do stuff, wait a second
about to set timeout with delay: 1000
delaying 1000
about to call do stuff 2nd time, wait .7 sec
about to set timeout with delay: 700
delaying 700
finished call do stuff
done with timeout
after call setTimeout with delay: 700
done with timeout
after call setTimeout with delay: 1000

When to Use Async/Await and When to Use .then

async/await is a drop in replacement for Promise syntax everywhere. Remember though that it cannot replace Promises in all cases, and async/await syntax does not absolve you from knowing about Promises.
The two main exceptions to async/await usage are:
1) Browser compatibility
Await is not supported in all browsers: https://caniuse.com/mdn-javascript_operators_await​
2) Asynchronous callbacks do not always need to happen sequentially.
Awaiting a callback result heavily implies that you need the result for the next callback.
const getData = async (category) => {
try {
const categories = await client.query(
'SELECT * from categories WHERE name= $1',
[category]
);
​
let categoryId = categories[0].id;
const recipes = await client.query(
'SELECT * FROM recipes WHERE category_id = $1',
[categoryId]
);
​
// ...
​
// catch errors thrown by client.query
} catch (error) {
console.log(error);
}
};
​
getData('vegan');
If you are getting unrelated data the code is more time efficient in a Promise.
const getData = async (category) => {
​
const categories = client.query(
'SELECT * from categories WHERE name= $1',
[category]
);
​
const users = client.query(
'SELECT * FROM users'
);
try{
// we don't care about which query finishes first
// but wait for both to be done before moving on
const results = await Promise.all([categories,users])
console.log(results);
} catch (error){
console.log(error);
}
};
​
getData('vegan');