Skip to main content

Command Palette

Search for a command to run...

33. ASYNC | async/await in JavaScript

This article will talk about async/await in javascript.

Updated
12 min read
S
Full-Stack Developer with 4 years' experience, specializing in backend development. Skilled in JavaScript, React, Python, Databases, and AWS. Known for building scalable web apps, leading teams, and maintaining strong client communication. Upskilling in Generative AI.

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)

  1. The entire script is loaded and pushed onto the call stack as the global execution context.

  2. Synchronous code (like console.log) executes line by line on the call stack.

  3. When a function (e.g., myFunc) is called, it is pushed onto the call stack and starts executing synchronously.

  4. 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.

  5. The remaining synchronous code inside the function continues executing.

  6. Once the function completes, it is removed from the call stack.

  7. The global script continues executing remaining synchronous code.

  8. When the call stack becomes empty, the event loop starts processing queued tasks:

    • First, it checks the microtask queue (Promises: .then, .catch, fetch resolution).

    • Then, it checks the callback queue (timers like setTimeout).

  9. The appropriate callback is moved from the queue to the call stack.

  10. The callback executes, and its output is printed.


What is an async function?

An async function always returns a settled promise.

  • Below async function 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 async function 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
*/
💡
Is there any difference in code execution flow when an asynchronous operation is called inside a regular function versus an 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 async function, you can use the await keyword.

  • This pauses the execution of the async function until the promise settles (either resolves or rejects).

  • Moves the remaining code (below await statement) of the async function 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.

  • await can only be used inside an async function (or top-level bodies of modules).

  • It does not block the entire JavaScript thread, only pauses that specific async function.

  • That specific async function exits the call stack as soon as it encounters the await statement.

  • fetch() always returns a promise. But when we use await keyword before fetch(), it returns Response object 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!