Asynchronous JavaScript
async / await
Master modern asynchronous JavaScript. Learn the async/await syntax, syntactic sugar over Promises, execution flow, and sequencing.
1. Introduction
ES2017 introduced async / await as syntactic sugar on top of Promises. It allows developers to write asynchronous, non-blocking code that looks and behaves like synchronous code, making complex control flows easier to read and maintain.
2. Why It Matters
Using raw Promises can result in complex .then() nesting chains. The async/await syntax flattens these chains, allows the use of standard language control structures (like try/catch or for loops), and improves stack trace readability during debugging.
3. Real-World Analogy
Think of a Smart Navigation App:
- Promise Chains (.then): Reaching a junction, stopping, reading a signpost, deciding the next turn, driving forward, and repeating at the next junction. The process is divided into separate, chained steps.
- async / await: Flipping on autopilot. You simply sit back and watch the car drive down the road. Even though the navigation system is executing complex calculations and waiting on traffic signals in the background, your journey feels like a single, continuous forward drive.
4. How It Works
The syntax consists of two keywords:
1. The async Keyword:
Placed before a function declaration. An async function always returns a Promise. If the function returns a value, the engine automatically wraps it in a resolved Promise.
2. The await Keyword:
Used only inside an async function. It pauses function execution until the awaited Promise settles, returning the resolved value or throwing the rejection error. While the function is paused, the main execution thread is released, allowing other tasks to run.
5. Sequential vs Parallel Execution
A common mistake when using async/await is executing independent requests sequentially instead of in parallel:
6. Practical Example
This script demonstrates executing a sequence of dependent tasks using async/await:
7. Common Mistakes
- Unintentional Sequential execution: Placing
awaiton independent requests immediately, which prevents them from running in parallel. - Forgetting to handle rejections: If an awaited Promise rejects, the function throws an error. If not wrapped in a
try/catchblock, the error becomes an uncaught exception, which can crash the application.
8. Quick Quiz
Q1: What does an async function return if you return a plain string value?
A) A string
B) A Promise that resolves to that string
Answer: B — Async functions always return a Promise. Any returned value is wrapped in a resolved Promise automatically.
9. Scenario-Based Challenge
The Parallel Asset Preloader:
An application preloads 5 assets. You have an async preloader function: preloadAsset(url). Write a function that triggers all 5 preloads in parallel and waits for all of them to finish before returning success.
10. Debugging Exercise
Explain why this code crashes, and how to fix it:
function getSetup() {
// Objective: Fetch configuration port
const res = await fetch('/setup.json'); // syntax bug!
return res.json();
}
getSetup();
View Solution
Diagnosis: The await keyword can only be used inside functions declared with the async modifier (except for top-level await in modules). Using it in standard functions throws a SyntaxError.
Fix: Add the async keyword to the function declaration:
async function getSetup() {
const res = await fetch('/setup.json');
return res.json();
}
11. Interview Questions
🟢 Q1: Explain how the JavaScript Engine pauses execution during an await statement without blocking the main thread.
Answer: When the engine hits an await statement, it suspends execution of the async function and saves its context (variable bindings and execution state) in the heap. It pops the function from the Call Stack, releasing the main thread to process other tasks (like user events or rendering). Once the awaited Promise resolves, a microtask is queued to resume the function. The engine then restores its context to the Call Stack and continues execution from where it was paused.
12. Production Considerations
- • Sequential Bottlenecks: When refactoring code to use
async/await, look for independent requests that are being awaited sequentially. Group them usingPromise.allto run them in parallel and improve performance.