Asynchronous code in JavaScript

JavaScript is single threaded, and relies on the Event loop and promises to handle asynchronous execution.

JavaScript offloads asynchronous operations to web APIs like fetch or setTimeout.

Promises

JavaScript is single-threaded, so when we execute something asynchronous we immediately get a promise in return

A promise have three states; 'pending', 'fulfilled', 'rejected'.

You can describe a JavaScript Promise as a promise of a future value.

JS

Copy

const somePromise = new Promise((resolve) => {
    setTimeout(() => {
        resolve('Resolved!');
    }, 1000)
})

console.log(somePromise) // Promise { <Pending> }

As one can see by the above code, when we console log somePromise, it returns a pending Promise.

How do we handle what happens when the promise is resolved?

We can use the .then() method of Promise, which is called when a promise is resolved.

JS

Copy

const somePromise = new Promise((resolve) => {
    setTimeout(() => {
        resolve("Resolved!");
    }, 1000)
})

somePromise
    .then((result) => console.log(result)) // Resolved! (after 1 second)

To handle a rejected Promise, we can use the .catch() method of Promise.

JS

Copy

const somePromise = new Promise((resolve, reject) => {
    reject("Rejected!")
})

somePromise
    .then((result) => console.log(result)) // is never called.
    .catch((error) => console.log(error)); // Rejected!

async await

You can also declare a function as async, and use the await keyword inside of it.

await stops execution of the block until the awaited promise is resolved or rejected

But as with any other promise, if we console.log the function, we get a Promise

We have to await the function call, to get the resolved value

JS

Copy

async function asyncFunction() {
    const response = await fetch('https://jsonplaceholder.typicode.com/users/1')
    return await response.json();
}

console.log(asyncFunction());      // Promise { <pending> }
console.log(await asyncFunction());// user json object

Event Loop

Since JavaScript is single-threaded it uses the Event Loop to manage asynchronous operations.

This is why when we console.log a Promise, or an async function we immediately see a Promise object

To understand why this is the case we need to understand the Event Loop.

The Event Loop manages the call stack, and two main queues, the macrotask queue and the microtask queue.


Synchronous code is added to the call stack, and executed immediately

Any .then() handler or code after an await statement are added to the microtask queue on Promise resolution

Callback APIs like setTimeout are added to the macro queue.


The Event Loop will execute everything in the call stack first, and when it's empty look in the microtask queue.

And only when both the call stack and microtask queue is empty will it look in the macrotask queue.

Consider the following code example, what is the order of execution?

JS

Copy

Promise.resolve().then(
    () => console.log(1)
)

setTimeout(() => console.log(2),0)

console.log(3)

Okay let's walk through it step by step.

alt

First we resolve a promise, and add the .then() handler to the microtask queue.


Then the setTimeout function is executed and we add its callback to the macrotask queue.


Then the console.log(3) is put on the call stack and executed.


The call stack is empty, so we execute any task in the microtask queue. In this case our .then() handler.

console.log(1) is executed.


Now the call stack and microtask queue is empty, we execute any task in the macrotask queue.

Which is our setTimeout callback, and console.log(2) is added to the call stack and executed.

So the order of the output of the code example is 3 1 2

JS

Copy

Promise.resolve().then(
    () => console.log(1)
)

setTimeout(() => console.log(2),0)

console.log(3)

// output
// 3
// 1
// 2