33. ASYNC | async/await in JavaScript
This article will talk about async/await in javascript.
First, let's understand the javascript code execution when an asynchronous operation is called inside a regular function.
1. Asynchronous Operation: Timer
console.log("====Start====");
function myFunc() {
console.log("Start of myFunc.");
// Timer: non-blocking, asynchronous
setTimeout(() => {
console.log("Hello World");
}, 0);
console.log("End of myFunc.");
}
myFunc();
console.log("====End====");
/* Output:
====Start====
Start of myFunc.
End of myFunc.
====End====
Hello World
*/
// ==========================================================
/* Execution Order:
1) The entire script is loaded and pushed onto the call stack as a global (anonymous) execution context.
2) Line 1: console.log("====Start====") will get executed.
3) Line 14: myFunc() is invoked and pushed onto the call stack.
4) Line 4: console.log("Start of myFunc.") will get executed.
5) Line 7: setTimeout() is encountered. It is handed over to the browser’s Web APIs where timer of 0 sec starts. The callback function is registered in Callback queue after the timer completes.
6) Line 11: console.log("End of myFunc.") will get executed.
7) After this, myFunc() finishes execution and is removed from the call stack.
8) Line 16: console.log("====End====") will get executed.
9) Once the call stack is empty, the event loop checks the callback queue.
10) The setTimeout callback (which logs "Hello World") is moved from the callback queue to the call stack.
11) The callback executes, and "Hello World" is printed.
*/
2. Asynchronous Operation: Promise
console.log("====Start====");
function myFunc() {
console.log("Start of myFunc.");
// Promise: non-blocking, asynchronous
const p = new Promise((resolve, reject) => {
if (true) {
resolve("Promise resolved successfully.");
} else {
reject("Promise rejected.");
}
})
p.then((data) => {
console.log(data);
})
.catch((err) => {
console.log(err);
})
console.log("End of myFunc.");
}
myFunc();
console.log("====End====");
/* Output:
====Start====
Start of myFunc.
End of myFunc.
====End====
Promise resolved successfully.
*/
// ==========================================================
/* Execution Order:
1) The entire script is loaded and pushed onto the call stack as a global (anonymous) execution context.
2) Line 1: console.log("====Start====") will get executed.
3) Line 25: myFunc() is invoked and pushed onto the call stack.
4) Line 4: console.log("Start of myFunc.") will get executed.
5) Line 7: A promise is created. It is handed over to the browser’s Web APIs for settlement. If promise is fulfilled, .then() callback function is registered in Microtask queue, else .catch() callback function is registered.
6) Line 22: console.log("End of myFunc.") will get executed.
7) After this, myFunc() finishes execution and is removed from the call stack.
8) Line 27: console.log("====End====") will get executed.
9) Once the call stack is empty, the event loop checks the microtask queue.
10) The .then() callback (which logs "Promise resolved successfully.") is moved from the microtask queue to the call stack.
11) The callback executes, and "Promise resolved successfully." is printed.
*/
3. Asynchronous Operation: Network Call
console.log("====Start====");
function myFunc() {
console.log("Start of myFunc.");
// Network Call: non-blocking, asynchronous
const url = 'https://6wrlmkp9u2.execute-api.us-east-1.amazonaws.com/?sleep=2000';
const p = fetch(url);
p.then((res) => {
return res.json();
})
.then((data) => {
console.log(data);
})
.catch((err) => {
console.log("===============", err);
})
console.log("End of myFunc.");
}
myFunc();
console.log("====End====");
/* Output:
====Start====
Start of myFunc.
End of myFunc.
====End====
=============== TypeError: Failed to fetch
*/
// ==========================================================
/* Execution Order:
1) The entire script is loaded and pushed onto the call stack as a global (anonymous) execution context.
2) Line 1: console.log("====Start====") will get executed.
3) Line 24: myFunc() is invoked and pushed onto the call stack.
4) Line 4: console.log("Start of myFunc.") will get executed.
5) Line 9: Fetch is encountered.
● The network request is delegated to the browser’s Web APIs.
● fetch immediately returns a Promise, which is stored in variable p.
● The .then() and .catch() handlers are registered, but they will execute only after the Promise settles.
● If the Promise is fulfilled, the .then() callbacks are added to the microtask queue.
● If the Promise is rejected, the .catch() callback is added to the microtask queue.
6) Line 21: console.log("End of myFunc.") will get executed.
7) After this, myFunc() finishes execution and is removed from the call stack.
8) Line 24: console.log("====End====") will get executed.
9) Once the call stack is empty, the event loop checks the microtask queue.
10) Since the network request fails, the Promise is rejected, and the .catch() callback is moved from the microtask queue to the call stack.
11) The callback executes, and the error message
"=============== TypeError: Failed to fetch" is printed.
*/
All three examples follow the same underlying pattern. The only real difference is which queue the callback goes to (microtask vs callback queue).
Generalized Execution Flow (Asynchronous operation inside a Regular Function)
The entire script is loaded and pushed onto the call stack as the global execution context.
Synchronous code (like
console.log) executes line by line on the call stack.When a function (e.g.,
myFunc) is called, it is pushed onto the call stack and starts executing synchronously.When an asynchronous operation is encountered (e.g.,
setTimeout,Promise,fetch):It is delegated to the browser’s Web APIs.
The main thread does not wait for it to complete.
Any associated callback (
setTimeout,.then,.catch) is registered for later execution.
The remaining synchronous code inside the function continues executing.
Once the function completes, it is removed from the call stack.
The global script continues executing remaining synchronous code.
When the call stack becomes empty, the event loop starts processing queued tasks:
First, it checks the microtask queue (Promises:
.then,.catch,fetchresolution).Then, it checks the callback queue (timers like
setTimeout).
The appropriate callback is moved from the queue to the call stack.
The callback executes, and its output is printed.
What is an async function?
An async function always returns a settled promise.
- Below
asyncfunction examples return promise in fulfilled state.
async function myFunc() {
// code logic here
}
console.log(myFunc());
async function myFunc() {
// code logic here
return "Hello World"
}
console.log(myFunc());
- Below
asyncfunction example returns promise in rejected state.
async function myFunc() {
// code logic here
throw new Error("An error occurred!");
}
console.log(myFunc());
Consuming return value from async function
async function myFunc() {
// code logic here
return "Hello World"
}
myFunc()
.then((data) => {
console.log(data);
})
// Output: Hello World
async function myFunc() {
// code logic here
throw new Error("An error occurred!");
}
myFunc()
.catch((err) => {
console.log(err);
})
/* Output:
Error: An error occurred!
at myFunc (script.js:3:11)
at script.js:6:1
*/
Now, let's see the code execution when we call an asynchronous operation inside an async function.
1. Asynchronous Operation: Timer
console.log("====Start====");
async function myFunc() {
console.log("Start of myFunc.");
// Timer: non-blocking, asynchronous
setTimeout(() => {
console.log("Hello World");
}, 0);
console.log("End of myFunc.");
}
myFunc();
console.log("====End====");
/* Output:
====Start====
Start of myFunc.
End of myFunc.
====End====
Hello World
*/
2. Asynchronous Operation: Promise
console.log("====Start====");
async function myFunc() {
console.log("Start of myFunc.");
// Promise: non-blocking, asynchronous
const p = new Promise((resolve, reject) => {
if (true) {
resolve("Promise resolved successfully.");
} else {
reject("Promise rejected.");
}
})
p.then((data) => {
console.log(data);
})
.catch((err) => {
console.log(err);
})
console.log("End of myFunc.");
}
myFunc();
console.log("====End====");
/* Output:
====Start====
Start of myFunc.
End of myFunc.
====End====
Promise resolved successfully.
*/
3. Asynchronous Operation: Network Call
console.log("====Start====");
async function myFunc() {
console.log("Start of myFunc.");
// Network Call: non-blocking, asynchronous
const url = 'https://6wrlmkp9u2.execute-api.us-east-1.amazonaws.com/?sleep=2000';
const p = fetch(url);
p.then((res) => {
return res.json();
})
.then((data) => {
console.log(data);
})
.catch((err) => {
console.log("===============", err);
})
console.log("End of myFunc.");
}
myFunc();
console.log("====End====");
/* Output:
====Start====
Start of myFunc.
End of myFunc.
====End====
=============== TypeError: Failed to fetch
*/
async function?Answer is No.
So, the next obvious question becomes:
What is the use of an async function?
An async function is a modern way to handle asynchronous operations, allowing you to write code that looks and behaves like synchronous code while remaining non-blocking.
The await Keyword
Inside an
asyncfunction, you can use theawaitkeyword.This pauses the execution of the
asyncfunction until the promise settles (either resolves or rejects).Moves the remaining code (below
awaitstatement) of theasyncfunction to the microtask queue.
Non-Blocking: While the async function's internal execution is paused at an await statement, async function exits the call stack ensuring rest of the application continues to run, preventing the browser or server from freezing.
1. Asynchronous Operation: Promise
console.log("====Start====");
async function myFunc() {
console.log("Start of myFunc.");
// Promise: non-blocking, asynchronous
const p = await new Promise((resolve, reject) => {
if (true) {
resolve("Promise resolved successfully.");
} else {
reject("Promise rejected.");
}
})
console.log(p);
console.log("End of myFunc.");
}
myFunc();
console.log("====End====");
/* Output:
====Start====
Start of myFunc.
====End====
Promise resolved successfully.
End of myFunc.
*/
// ==========================================================
/* Execution Flow:
1) The entire script is loaded and pushed onto the call stack as the global (anonymous) execution context.
2) Line 1 executes: console.log("====Start===="); is printed.
3) myFunc() is called and pushed onto the call stack.
4) Inside myFunc, Line 4 executes: "Start of myFunc." is printed.
5) At the await expression:
● A new Promise is created.
● The executor runs immediately, and since the condition is true, resolve("Promise resolved successfully.") is called.
● Even though the Promise resolves immediately, await pauses the execution of myFunc.
● The remaining part of myFunc is scheduled to resume later as a microtask.
● At this point, myFunc exits the call stack (it returns a pending Promise).
6) Line after the function call executes: console.log("====End===="); is printed.
7) Once the call stack becomes empty, the event loop checks the microtask queue.
8) The paused execution of myFunc (after await) is moved back to the call stack.
9) Execution resumes inside myFunc: console.log(p); prints: "Promise resolved successfully."
10) Next line executes: "End of myFunc." is printed.
11) myFunc completes execution.
*/
2. Asynchronous Operation: Network Call
console.log("====Start====");
async function myFunc() {
console.log("Start of myFunc.");
// Network Call: non-blocking, asynchronous
const url = 'https://dummyjson.com/test';
const res = await fetch(url);
const data = await res.json();
console.log("Data received from API:", data);
console.log("End of myFunc.");
}
myFunc();
console.log("====End====");
/*
====Start====
Start of myFunc.
====End====
Data received from API: {status: 'ok', method: 'GET'}
End of myFunc.
*/
// ==========================================================
/* Execution Flow:
1) The entire script is loaded and pushed onto the call stack as the global (anonymous) execution context.
2) Line 1 executes: console.log("====Start===="); is printed.
3) myFunc() is called and pushed onto the call stack.
4) Inside myFunc, Line 4 executes: "Start of myFunc." is printed.
5) At await fetch(url):
● The network request is delegated to the browser’s Web APIs.
● fetch immediately returns a Promise.
● Since await is used, execution of myFunc is paused here.
● The remaining part of the function is scheduled to resume later as a microtask.
● myFunc exits the call stack (returns a pending Promise).
6) Next line in the global scope executes: console.log("====End===="); is printed.
7) Once the network request completes:
● The Promise returned by fetch is resolved.
● The continuation of myFunc (after the first await) is placed in the microtask queue.
8) The event loop moves this microtask to the call stack, and execution resumes:
● res now contains the response object.
9) At await res.json():
● Another Promise is returned for parsing JSON.
● Execution pauses again.
● The remaining part is scheduled as a microtask.
10) When JSON parsing completes:
● The Promise resolves.
● The continuation is again placed in the microtask queue.
11) The event loop processes this microtask:
● data now contains the parsed response.
● console.log("Data received from API:", data); is executed.
● "End of myFunc." is printed.
12) myFunc completes execution.
*/
Consuming return value from async function
async function myFunc() {
// Network Call: non-blocking, asynchronous
const url = 'https://dummyjson.com/test';
const res = await fetch(url);
const data = await res.json();
return data
}
myFunc()
.then((data) => {
console.log("=====", data);
});
// Output: ===== {status: 'ok', method: 'GET'}
More on await keyword
Without
await, you typically handle Promises using.then()and.catch(), which can become hard to read when chained.With
await, asynchronous code looks and behaves more like synchronous code, making it easier to understand and maintain.awaitcan only be used inside anasyncfunction (or top-level bodies of modules).It does not block the entire JavaScript thread, only pauses that specific
asyncfunction.That specific
asyncfunction exits the call stack as soon as it encounters theawaitstatement.fetch()always returns a promise. But when we useawaitkeyword beforefetch(), it returnsResponseobject not promise.
Using await in top-level bodies of modules
Ensure to load javascript as module (type = module) in html file.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>async/await in JavaScript</title>
<script src="script.js" type="module"></script>
</head>
<body style="font-family: cursive">
<h1>async/await in JavaScript</h1>
</body>
</html>
const url = 'https://dummyjson.com/test';
const res = await fetch(url);
const data = await res.json();
console.log("=====", data);
// Output: ===== {status: 'ok', method: 'GET'}
Happy learning!

