A network request answers in anything from a few dozen milliseconds to several seconds. Without await, the rest of the work has to be described inside a function handed to .then(), and the code reads backwards as soon as there are three steps.
await puts the instructions back in order. The next line only runs once the result has arrived, as if the wait had been instant.
Definition
await suspends the function containing it until the Promise on its right settles, then resumes with the resulting value.
async function loadCustomer(id) {
const response = await fetch('/api/customers/' + id);
const customer = await response.json();
return customer.name;
}Two waits, two lines, in reading order. Without the keyword, response would hold a promise rather than a response, and the next line would fail on a method it cannot find.
await does not freeze the browser. It pauses the current function, not the page: animations keep running, clicks are received, the rest of the program executes. That is the difference between waiting and blocking.
Where it is allowed to appear
Two places, no more: inside the body of an async function, and at the top level of a module. Anywhere else, the file refuses to load.
// inside an async function
async function main() {
const data = await load();
}
// at the top level of a module
const data = await load();Placed in front of an ordinary value, await returns it unchanged. So there is no need to check whether what you are waiting for is really a promise: the keyword handles both.
In series or in parallel
This is the mistake that costs the most, and it is invisible on reading. Two consecutive await lines wait one after the other, even when the two operations are independent.
const wait = (ms, v) => new Promise(r => setTimeout(() => r(v), ms));
// in series: about 120 ms
const a = await wait(60, 'a');
const b = await wait(60, 'b');
// in parallel: about 60 ms
const [x, y] = await Promise.all([wait(60, 'x'), wait(60, 'y')]);The rule fits in one sentence: chain your await lines when the second operation needs the first one's result, and start them together otherwise. On a page loading five resources, the difference is measured in seconds.
Handling failure
A rejected promise turns the await line into an ordinary error, so it is caught with try and catch.
try {
const customer = await loadCustomer(7);
render(customer);
} catch (error) {
console.error('Could not load:', error.message);
}Without that safety net, the failure surfaces as an unhandled rejection, visible in the console but invisible to the user, who is left staring at an empty screen with no explanation.
Frequently asked questions
What happens if I forget the keyword?
The variable receives the promise object instead of the value you expected. The usual symptom is a screen showing [object Promise], or a property reading undefined even though the data did arrive. Whenever you see that word inside a value, go looking for the missing await.
Can I await inside a loop?
Yes, and it is the normal approach in an of loop, for instance to process items one by one without overwhelming a server. Be careful with Array.forEach(), though, which does not wait: the loop finishes before the work does and the result looks empty.
await or .then(), which one should I pick?
Both do the same job, and nothing forbids mixing them in one project. await wins as soon as there are several steps or any error handling, because the code keeps the shape of a linear read. .then() stays convenient for a single piece of work attached to a promise on the fly.