Internal behavior of async/await and how it works under the hood

Ramkumar Khubchandani
2 min readOct 13, 2024

--

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.

--

--

Ramkumar Khubchandani
Ramkumar Khubchandani

Written by Ramkumar Khubchandani

Frontend Developer|Technical Content Writer|React|Angular|React-Native|Corporate Trainer|JavaScript|Trainer|Teacher| Mobile: 7709330265|ramkumarkhub@gmail.com

No responses yet