How async and await work
At a high level, async and await in JavaScript are syntax built on top of Promises. They let you write asynchronous code in a style that reads top-to-bottom.
What async does
Declaring a function as async means the function always returns a Promise.
- Returned values are wrapped in a fulfilled Promise.
- Thrown errors become a rejected Promise.
async function f() {
return 42;
}
This is equivalent to:
function f() {
return Promise.resolve(42);
}
If the function throws, the returned Promise is rejected:
async function f() {
throw new Error("oops");
}
Equivalent to:
function f() {
return Promise.reject(new Error("oops"));
}
What await does
await pauses the current async function until its operand settles.
const result = await somePromise;
Behavior:
- If the awaited value fulfills,
awaitevaluates to the fulfillment value. - If rejection occurs,
awaitthrows the rejection reason.
await also accepts non-Promise values. They are converted with Promise.resolve(...), so this is valid:
const x = await 5; // x === 5
For error handling, use try/catch inside async functions:
try {
const data = await fetchData();
} catch (err) {
// handles rejection
}
Example: Promise vs async/await
Using Promises:
fetchData()
.then(data => process(data))
.then(result => console.log(result))
.catch(err => console.error(err));
Using async/await:
async function run() {
try {
const data = await fetchData();
const result = process(data);
console.log(result);
} catch (err) {
console.error(err);
}
}
Both versions express the same flow. The async/await form is usually easier to read and debug.
Important: no thread blocking
Even though await looks like a pause point, JavaScript remains non-blocking.
Under the hood:
- The function pauses
- Control returns to the event loop
- Other code can run
- When the Promise resolves, execution resumes
Execution flow (mental model)
When you hit an await:
- Evaluate the expression and convert the result to a Promise when needed.
- Suspend the function
- Return control to the event loop
- Resume the function when that Promise settles
Sequential vs parallel awaits
Sequential (often slower; runs one after another):
const a = await getA();
const b = await getB();
Parallel start (often faster):
const [a, b] = await Promise.all([getA(), getB()]);
Note: Promise.all is fail-fast. If any Promise rejects, the whole await Promise.all(...) throws.
Common pitfalls
Forgetting await:
const data = fetchData(); // still a Promise!
Using await outside async contexts:
await fetchData(); // SyntaxError in scripts (allowed in ES modules)
Accidental serialization:
for (const item of items) {
await process(item); // runs one-by-one
}
When work is independent, parallelize:
await Promise.all(items.map(process));
Summary of mental model
asyncmakes a function return a Promise.awaitextracts a fulfillment value or throws on rejection.awaitpauses only the current async function, not the JavaScript thread.async/awaitis Promise-based syntax, not a different concurrency model.