1. Introduction
async/await is syntax introduced in ES2017 that lets you write asynchronous code that looks and reads like synchronous code, while still being built entirely on top of Promises. The async keyword marks a function as asynchronous, and inside it, the await keyword pauses execution of that function (without blocking the rest of the program) until a Promise settles, then resumes with either its resolved value or a thrown error.
Cricket analogy: A batsman waiting between deliveries doesn't leave the crease or stop the match clock for others; the umpire's signal, the Promise settling, lets play resume exactly where it paused, with either a run scored or a wicket falling.
2. Syntax
async function fetchData() {
try {
const result = await someAsyncOperation();
return result; // wraps the return value in a resolved Promise
} catch (error) {
console.error("Failed:", error);
throw error; // re-throw, or handle it here
}
}
// Running steps in parallel instead of sequentially
async function fetchBoth() {
const [a, b] = await Promise.all([fetchA(), fetchB()]);
return { a, b };
}
// Top-level await is allowed inside ES modules
const data = await fetchData();3. Explanation
async/await is 'syntactic sugar' over Promises: an async function ALWAYS returns a Promise, even if you write a plain return value; -- JavaScript automatically wraps that value in Promise.resolve(value). If the function throws, the returned Promise is rejected with that error instead. Inside an async function, await somePromise pauses that function's execution until the Promise settles, then either returns the resolved value or throws the rejection reason, which you can catch with an ordinary try/catch block -- unlike plain callbacks, this works correctly because the throw happens synchronously from the perspective of the async function's own execution context.
Cricket analogy: Every over a bowler completes is automatically logged into the match scorecard, the Promise, whether they wanted it recorded or not, and a no-ball, the thrown error, gets flagged the same way so the umpire can review it immediately.
A frequent mistake is calling await sequentially for independent operations, e.g. await fetchA(); await fetchB();, which needlessly runs them one after another. If A and B don't depend on each other, starting both Promises first and then awaiting Promise.all([fetchA(), fetchB()]) runs them concurrently and finishes much sooner.
Cricket analogy: Waiting for the pitch report to finish before separately waiting for the weather report wastes time when both could be checked in parallel by two different analysts, finishing together instead of one after another.
await can only be used directly inside a function declared async (or at the top level of an ES module). Using it in a regular function is a SyntaxError. Also, because an async function returns a Promise, forgetting to await or .catch() its result can produce an 'unhandled promise rejection' if it throws -- the error will not surface as a normal synchronous exception.
4. Example
function delay(ms, value) {
return new Promise((resolve) => setTimeout(() => resolve(value), ms));
}
async function main() {
console.log("A - start of async function");
const user = await delay(100, "Alice");
console.log("B - got user:", user);
const orders = await delay(100, ["order1", "order2"]);
console.log("C - got orders:", orders);
return "done";
}
console.log("1 - before call");
main().then((result) => console.log("D - result:", result));
console.log("2 - after call");5. Output
1 - before call
A - start of async function
2 - after call
B - got user: Alice
C - got orders: [ 'order1', 'order2' ]
D - result: done6. Key Takeaways
- An async function ALWAYS returns a Promise, even if the return statement returns a plain value.
awaitruns synchronously up to the first await -- code before it executes immediately when the function is called.awaitcan only be used inside an async function (or at the top level of a module).- try/catch around await correctly catches Promise rejections, unlike try/catch around a raw callback.
- Awaiting independent operations sequentially wastes time; use Promise.all to run them concurrently.
- A rejected Promise from an async function that is never awaited or caught causes an unhandled promise rejection.
Practice what you learned
1. What does an async function return, even if its body only contains `return 5;`?
2. Where can the `await` keyword be legally used?
3. In `async function f(){ console.log('X'); await null; console.log('Y'); } console.log('1'); f(); console.log('2');`, what prints third?
4. Why is `await fetchA(); await fetchB();` slower than `await Promise.all([fetchA(), fetchB()])` when A and B are independent?
5. What happens if a Promise returned by an async function rejects and nothing ever awaits or .catches it?
Was this page helpful?
You May Also Like
Promises in JavaScript
Understand the Promise object, its three states, and how .then/.catch/.finally chaining replaces nested callbacks.
Callbacks and Asynchronous JavaScript
Learn how JavaScript handles operations that take time using callback functions, and why deeply nested callbacks lead to callback hell.
Fetch API in JavaScript
Use the Fetch API to make HTTP requests, and learn the critical gotcha that fetch does not reject on HTTP error status codes.
The Event Loop in JavaScript
Understand the call stack, macrotask queue, and microtask queue, and master the ordering rules that govern async execution.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics