Internal behavior of async/await and how it works under the hood
The Basics of Async/Await
Async/await is a syntactic feature that allows you to write asynchronous code that looks and behaves like synchronous code. It’s built on top of Promises and generators.
Example:
async function fetchData() {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
return data;
}
How it Works Internally
When you declare a function as async
, the JavaScript engine wraps the function's body in a Promise. The await
keyword is used to wait for a Promise to resolve before continuing execution.
Here’s what happens under the hood:
a. The async
function is converted into a state machine. b. Each await
expression marks a point where the function can be suspended. c. When an await
is encountered, the function yields control back to the caller. d. Once the awaited Promise resolves, the function is resumed from where it left off.
The State Machine
Let’s look at how a simple async function might be transformed into a state machine:
async function example() {
console.log('Start');
await Promise.resolve();
console.log('End');
}
This could be roughly translated to:
function example() {
return new Promise((resolve) => {
let state = 0;
function next() {
switch (state) {
case 0:
console.log('Start');
state = 1;
Promise.resolve().then(next);
break;
case 1:
console.log('End');
resolve();
break;
}
}
next();
});
}
The Event Loop
Async/await leverages JavaScript’s event loop. When an await
is encountered:
a. The current function is paused.
b. The awaited Promise is added to the microtask queue.
c. The JavaScript engine continues executing other code.
d. When the call stack is empty, the event loop checks the microtask queue.
e. If the Promise has resolved, the function resumes execution.
Error Handling
Async/await also simplifies error handling. You can use try/catch blocks as you would with synchronous code:
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
return data;
} catch (error) {
console.error('An error occurred:', error);
}
}
Internally, this is handled by the Promise’s rejection mechanism.
Parallel Execution:
While await
waits for one Promise at a time, you can still achieve parallel execution:
async function fetchMultipleData() {
const [data1, data2] = await Promise.all([
fetch('https://api.example.com/data1').then(r => r.json()),
fetch('https://api.example.com/data2').then(r => r.json())
]);
return { data1, data2 };
}
Here, both fetches start in parallel, and await
is used on the combined Promise.
Understanding these internal mechanics can help you write more efficient asynchronous code and debug issues more effectively.
Do You want to learn more like above content ?
Follow me or message me on Linkedin.