JavaScript asynchronous and the event loop

This article walks you through all things about asynchronous JavaScript including why asynchronous is needed, how it works for a single-threaded JavaScript and how to do asynchronous programing. A lot of content is from MDN but reorganized for a better understanding.

JavaScript is an inherently single-threaded language. By default (unless you uses a web worker which is provided by the browser, not part of the JavaScript language) , the browser uses a single thread (the main thread) to run all the JavaScript in your page and to perform other stuff like event receiving/dispatching, UI updating. But for long-running operations, making them asynchronous is needed.

All content talked here if not specified, is about JavaScript in a browser environment.

Why asynchronous programming is needed?

A basic problem with a long-running synchronous function is that it will block the thread and make our program unresponsive. To avoid the function to block the thread we can make the long-running function being asynchronous like:

  • When it is called it returns immediately and our program can still be responsive.

  • When it eventually completes our program can be notified with its result.

Many functions provided by browsers take a long time, therefore they are asynchronous. Some of them are (see more ):

Note

Such Browser APIs are built into your web browser and are not part of the JavaScript language itself. They are built on top of the core JavaScript language and executed by the browser itself, not by the JavaScript engine which executes your JavaScript code.

See more in Introduction to web APIs – Learn web development | MDN.

How asynchronous functions are implemented?

About how asynchronous function is implemented, you might have thought of the way an event is handled. You are right, in fact some early asynchronous APIs used events in just this way.

JavaScript has a runtime model based on an event loop. The event loop processes tasks one after another. It is something like:

// queue.waitForMessage() waits synchronously for a message to arrive
// (if one is not already available and waiting to be handled).
while (queue.waitForMessage()) {
  queue.processNextMessage();
}

The event loop

JavaScript has a runtime model based on an event loop, which is responsible for executing the code, collecting and processing events, and executing queued sub-tasks.

When an event occurs, its handler(callback) will be added into the task queue and run at an appropriate time. This is really a form of asynchronous programming.

Some early asynchronous APIs used events in just this way. Like you call XMLHttpRequest to make a HTTP request to a remote server and you need to define a listener for the loadend event to get notified about the eventual completion of the request.

const xhr = new XMLHttpRequest();
xhr.addEventListener('loadend', () => {
    console.log(`Finished with status: ${xhr.status}`);
});

xhr.open('GET', 'https://example.com/example.json');
xhr.send();

The asynchronous function returns immediately and the request will be going on in the browser. At the same time our JavaScript code which is executed by the JavaScript engine continues to run. And our event handler will be called when the request is complete.

Task queue

Here it just mentions one queue for simplicity. In fact, there can be more than one queues so that events/messages can have different priorities and they are put into different queues.

Promise

Now you have known the way how asynchronous functions were implemented in JavaScript. However, sometimes callback-based code will become not elegant . Like we need preform an operation that needs a series of asynchronous functions, we nest callbacks like:

function doOperation() {
  doStep1(0, (result1) => {
    doStep2(result1, (result2) => {
      doStep3(result2, (result3) => {
        console.log(`result: ${result3}`);
      });
    });
  });
}

doOperation();

This is called “callback hell” and it needs to handle errors at each level.

For these reasons, most modern asynchronous APIs don’t use callbacks but Promise. But the mechanism behind is similar.

Promise

Basic usage

You can think a promise as an object returned by an asynchronous function. When the promise is returned, the operation often isn’t finished. But it allows you to attach handlers to this promise object. When the operation is finished, the promise will call our handler asynchronously, passing the final result or an error of the operation as the parameter.

Promise description

Promise is a proxy for a value not necessarily known when the promise is created. This lets asynchronous methods return values like synchronous methods, merely it returns a Promise and supply the final value in the future.

Below is an example to make a HTTP request to a remote server with the fetch() API that is based on promise.

const fetchPromise = fetch('https:example.com/p1.json');

// Here the fetchPromise's state is "pending"
console.log(fetchPromise); // Output: Promise {}

// When the fetch operation succeeds, fetchPromise gets resolved with
// a Response object returned by fetch as its value
// and fetchPromise calls our handler, passing its value as the parameter.
fetchPromise.then((response) => {
    // Output: Received response: 200
    console.log(`Received response: ${response.status}`);

    console.log(fetchPromise); // Output: Promise {: Response}
});

The then() takes up to two parameters: onFulfilled and onRejected. They are callback functions (handlers) for the success and failure cases of the Promise. Both of them are optional.

Note:

If onFulfilled is not a function, it is internally replaced with an identity function ((x) => x) which simply passes the fulfillment value forward.

If onRejected. is not a function, it is internally replaced with a thrower function ((x) => { throw x; }) which throws the rejection reason it received.

It returns a newly generated Promise. When a handler(onFulfilled/onRejected) is called, the promise gets resolved with the value returned by the handler as its value (if the handler does return a value and it is not a promise).

Basically if the handler is called and:

  • doesn’t return anything, the promise get resolved with undefined as its value.

  • returns another promise (let’s call it B), the promise (let’s call it A) returned by then() follows it.

    A follows B means if B gets resolved, A gets resolved, if B never gets resolved, A won’t either.

See more about the rules that the behavior of the handler follows at Promise.prototype.then() – JavaScript | MDN.

Chain promises

As you saw above, then() returns a promise so that it can be used for chaining.

Below is an examples which shows how to chain multiple promises to preform an operation that needs a series of asynchronous functions as mentioned in the previous section.

doAsyncStep1(0)
    .then((result1) => doAsyncStep2(result1))
    .then((result2) => doAsyncStep3(result2))
    .then((result3) => {
        console.log(`result: ${result3}`);
    });

In this example, each subsequent asynchronous function starts when the previous one succeeds, with the parameter comes from the previous step.

Always return result in chain

In the above example, each onFulfilled handler is an arrow function with only an expression which is the implicit return value.

In other cases, if the arrow function has a block body or a normal function, you need an explicit return statement, otherwise the subsequent handlers won’t get the result of the previous one. And a worse issue is that the next handler will be called earlier because the previous one returns nothing and make the corresponding promise gets resolved with undefined as its value.

See at Using Promises.

Below is an example that gets the response data after a request is completed. See at How to use promises – chaining promises

const fetchPromise = fetch('http://example.com/p1.json');

fetchPromise
    .then((response) => response.json()) // Here it does not handle
        // the situation that respone.ok is not true
    .then((data) => {
        console.log(data[0].name);
    });

The json() method of the Response object is also asynchronous and returns a promise.

More about the then()

Below is another example that shows the process of the then() is executed.

function foo() {
    const p = new Promise((resolve, reject) => {
        resolve('Success');
    });

    console.log('p', p); // p: Promise {:

    return p.then((value) => {
        console.log(value); // 'Success'
        return 'Success2';
    }, (e) => {
        console.log(e.message);
    });
}

const result = foo();
console.log('Result:', result); // Result: Promise {}
// By using setTimeout, we postpone the execution of `console.log(result)`
setTimeout(() => console.log(result), 0);

// Output:
// p: Promise {: 'Success'}
// Result: Promise {}
// Success
// Promise {: 'Success2'}

Here is how:

  1. When you call foo(), it first create a resolved promise p. It is printed in the console: p: Promise {: 'Success'}.

  2. After that the then() is called on the promise p. then() creates and returns a pending promise which is then returned by foo().

    Note the handlers added to then() are always called asynchronously. Even though here p is already fulfilled, the handler of p.then() will not be called immediately but is put on a microtask queue. See more at Using Promises: Timing.

  3. The pending promise is printed in the console: Result: Promise {}.

  4. setTimeout postpone the execution of console.log(result).

  5. At some point, onFulfilled handler of then() is called, and Success is printed in the console. The promise returned by then() gets resolved with Success as its value.

  6. At some point, console.log(result) is executed and the promise which has get resolved is printed in the console:

    {: 'Success'}

In the example, if p is a rejected promise that is:

const p = new Promise((resolve, reject) => {
    // or throw new Error(’fail');
    reject(new Error('Fail'));
});

The output would be:

// p: Promise {<rejected>: Error: Fail
// Result: Promise {<pending>}
// Fail
// Promise {<fulfilled>: undefined}

Attaching handlers for a promise with then() is like defining a handler for an event. The handler in then() is called asynchronously at some point in the event loop.

Using Promises – Guarantees

Unlike old-fashioned passed-in callbacks, a promise comes with some guarantees:

  • Callbacks added with then() will never be invoked before the completion of the current run of the JavaScript event loop.
  • These callbacks will be invoked even if they were added after the success or failure of the asynchronous operation that the promise represents.
  • Multiple callbacks may be added by calling then() several times. They will be invoked one after another, in the order in which they were inserted.

See more at Promise.prototype.then() and Let’s talk about how to talk about promises.

Error handling with catch()

The Promise object provide a catch() method to handle errors. Like then(), the handler passed to catch() is called when the asynchronous operation fails.

An example with error handling.

const fetchPromise = fetch('https:example.com/p1.json');

fetchPromise
    .then((response) =&gt; {
        if (!response.ok) {
            throw new Error(`HTTP error: ${response.status}`);
        }
        return response.json();
    })
    .then((data) =&gt; {
        console.log(data[0].name);
    })
    .catch((error) =&gt; {
        console.error(`Could not get products: ${error}`);
    });

finally():

The finally() method can be useful if you want to do some processing or cleanup once the promise is settled, regardless of its outcome.

The main differences between finally(finally) and then(onFinally, onFinally) it that the return value of the handler of finally() does not affect the state of the original promise, unless the handler throws an error or returns a rejected promise.

// The returned promise will gets resolved with 2
console.log( Promise.resolve(2).finally(() =&gt; 77) );

// The returned promise will gets resolved with 2
console.log( Promise.resolve(2).finally(() =&gt; Promise.resolve(77)) );

// The returned promise will gets rejected with 99
console.log( Promise.reject(3).finally(() =&gt; Promise.reject(9
9)) );
// The returned promise will gets rejected with 99
console.log( Promise.reject(3).finally(() =&gt; {throw 99}) );

About return value

catch() also returns a Promise and it can be used for chaining too. But unlike then(), it has one handler and the handler may have no chance to be called. This may remind you a question. Like the example showed in the previous section, if a function returns a promise with catch() attached then how the returned promise gets resolved.

Promise.prototype.catch() tells that catch() internally calls then() and gives an example to observe that. More clearly, catch() internally calls then() with parameters undefined and its own onRejected handler. And it returns the value that then() returns.

In fact, catch() acts just like a then(undefined, onRejected). Hence it is clear what happens in the example below.

An example in which a catch() is chained to a promise.

function foo() {
    const p = new Promise((resolve, reject) =&gt; {
        resolve('Success');
    });

    console.log('p:', p); // p: Promise {: 'Success'}

    return p.catch((e) =&gt; {
        console.error('Fired in catch:', e.message); // Never called.
    });
}
const result = foo();
console.log('Result:', result); // Result: Promise {}
// By using setTimeout, we postpone the execution of `console.log(result)`
setTimeout(() =&gt; console.log(result), 0); // Promise {: 'Success'}

// Output:
// p: Promise {: 'Success'}
// Result: Promise {}
// Promise {: 'Success'}

Promise – JavaScript | MDN:

.catch() is really just a .then() without a slot for a callback function for the case when the promise is fulfilled.

A rejected callback in then() or a catch()

If there is no need to handle errors immediately, you can just leave out an error in a promise chain to fall back to the final catch(). Let’s rewrite the example by adding error handling:

doAsyncStep1(0)
    .then((result1) =&gt; doAsyncStep2(result1))
    .then((result2) =&gt; doAsyncStep3(result2))
    .then((result3) =&gt; {
        console.log(`result: ${result3}`);
    })
    .catch((error) =&gt; {
        // The error callback will be called when any of the
        // asynchronous doAsyncStepX calls fails.
        console.error(error);
    });

If you need to handle an error immediately, just provide a rejection callback to a proper then(). Note if that rejection callback is called, you need to throw an error to make the error down the chain, otherwise the promise returned by that then() will get fulfilled.

Gotchas when throwing errors:

Errors thrown inside asynchronous functions will act like uncaught errors:

const p2 = new Promise((resolve, reject) =&gt; {
    setTimeout(() =&gt; {
        throw new Error("Uncaught Exception!");
    }, 1000);
});

p2.catch((e) =&gt; {
    console.error(e); // This is never called
});

Another eample:

function foo(request) {
    // caches.match() gets the reponse for a request in the caches.
    return caches.match(request)
        .then(undefined, error =&gt; {
            // Error in the asynchronous open() won't be caught
            // in the final catch.
            caches.open();

            // Its error will be caught in the final catch,
            // because the promise A returned by then() follows
            // the promise B returned by match(). When B gets
            // gets rejected, A gets rejected either and
            // the handler in the final catch will be called.
            return catches.match('/default.jpg');
        })
        .catch(function (error) {
            console.log('final match error:', error);
        });
}

Errors thrown after resolve is called will be silenced:

const p3 = new Promise((resolve, reject) =&gt; {
    resolve();
    hrow new Error("Silenced Exception!");
});

p3.catch((e) =&gt; {
    console.error(e); // This is never called
});

Promise.all() and Promise.any()

If you need to be notified when all the promises to be fulfilled which don’t depend on each other, Promise.all() is what you want.

Promise.all() is a static method which takes an array of promises and returns a single promise. The returned promise is fulfilled when and if all the promises in the array are fulfilled, and it is rejected when and if any of the promises is rejected.

Example:

const fetchPromise1 = fetch('https://example.com/p1.json');
const fetchPromise2 = fetch('https://example.com/p2.json');
const fetchPromise3 = fetch('https://example.com/p3.json');

Promise.all([fetchPromise1, fetchPromise2, fetchPromise3])
    .then((responses) =&gt; {
        for (const response of responses) {
            console.log(`${response.url}: ${response.status}`);
        }
    })
    .catch((error) =&gt; {
        console.error(`Failed to fetch: ${error}`)
    });

If you need any one of a set of promises to be fulfilled not all, you will want Promise.any(). Its usage is similar and not listed hare any more.

States of a promise

A promise can be in one of three states:

  • pending: initial state, the asynchronous function has not completed.
  • fulfilled: the asynchronous function has succeeded, the first handler(onFulfillment parameter) passed to then() is called.
  • rejected: the asynchronous function has failed, its catch() handler is called.

Terms: settled, resolved

A promise is settled if it is fulfilled or rejected.

A promise is resolved if it is settled or it has been “locked in” to follow the state of another promise (by attaching then() to it).

See more in Let’s talk about how to talk about promises.

The eventual state of a pending promise can either be fulfilled with a value or rejected with a reason (error).

See more at Promise and How to use promises.

Promise constructor

You have seen how to create a Promise in the examples mentioned above. It accepts a function called executor which will be executed by the constructor. The function executor accepts will receive two functions  resolutionFunc and rejectionFunc as parameters.

The constructor returns a promise which will become resolved when either of the functions resolutionFunc or rejectionFunc are invoked.

Note that when you call resolutionFun(value) or rejectionFunc(value), the value can also be another promise, in which case the promise gets dynamically inserted into the promise chain (In such case, the constructed promise is resolved but still not settled).

Below example wraps a callback-style asynchronous to support promise.

function myAsyncFunction(url) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open("GET", url);
        xhr.onload = () => resolve(xhr.responseText);
        xhr.onerror = () => reject(xhr.statusText);
        xhr.send();
    });
}

See more at MDN: Promise() constructor.

Browser support of promises

JavaScript Promises: an introduction:

Browser support & polyfill

There are already implementations of promises in browsers today.

As of Chrome 32, Opera 19, Firefox 29, Safari 8 & Microsoft Edge, promises are enabled by default.

To bring browsers that lack a complete promises implementation up to spec compliance, or add promises to other browsers and Node.js, check out the polyfill (2k gzipped).

Async and await

The async and await keywords provides a cleaner way to work with promise-based code, avoiding explicitly configuring promise chains.

Adding async at the start of a function makes it an async function. Inside an async function, you can put await before a call to a function that returns a promise.

Let’s see an example to find out the effect of a await expression.

async function foo() {
    console.log('foo'); // It will be executed synchronously.

    // An await expression will make the progress suspended.
    const result1 = await new Promise((resolve) =>
        setTimeout(() => resolve('1'))
    );
    console.log('result1 =', result1); // Called asynchoronously

    const result2 = await new Promise((resolve) =>
        setTimeout(() => resolve('2'))
    );
    console.log('result2 =', result2);
}

console.log('before foo');
foo(); // Here foo() returns a promise with pending state.
console.log('after foo');

// Output:
// before foo
// foo
// after foo
// result1 = 1
// result2 = 2

Code after each await expression (including the assignment of the await expression) acts like the code inside a handler of a then().

The effect is equivalent to:

function foo() {
    console.log('foo');

    new Promise((resolve) =>
        setTimeout(() => resolve('1'))
    ).then((result1) => {
        console.log('result1 =', result1);
    }).then(() => {
        return new Promise((resolve) =>
           setTimeout(() => resolve('2'))
        );
    }).then((result2) => {
        console.log('result2 =', result2);
    })
}

await

Note that you can only use await inside an async function, unless your code is in a JavaScript module.

Return value of an async function

An async function always return a promise.

If the async function returns:

  • returns a value that is not a promise, the promise will gets resolved with that value.

  • throws an error, or uncaught within, the promise gets rejected with the error.

  • returns nothing, the promise will not gets resolved or rejected, i.e. remains pending.

Error handling

async function foo() {
    console.log('foo');

    const result1 = await new Promise((resolve) =>
        setTimeout(() => resolve('1'))
    );
    console.log('result1 =', result1);

    const result2 = await new Promise((resolve, reject) => {
        setTimeout(() => reject('fail'));
    });
    console.log('result2 =', result2);
}

console.log('before foo');
foo()
    .catch((e) => console.log('catch:', e));
console.log('after foo');

// Output:
// before foo
// foo
// after foo
// result1 = 1
// catch: fail

If there is no catch in the example:

console.log('before foo');
var fooResult = foo();
console.log('after foo');
setTimeout(() => console.log('after foo, fooResult =', fooResult), 1000);

// Output:
// before foo
// foo
// after foo
// result1 = 1
// Uncaught (in promise) fail
// after foo, fooResult = Promise {<rejected>: 'fail'}

An parallel example

async function concurrentStart() {
    console.log("==CONCURRENT START with await==");
    const slow = resolveAfter2Seconds(); // starts timer immediately
    const fast = resolveAfter1Second(); // starts timer immediately

    // 1. Execution gets here almost instantly
    console.log(await slow); // 2. this runs 2 seconds after 1.
    console.log(await fast); // 3. this runs 2 seconds after 1., immediately after 2., since fast is already resolved
}

Async function vs a function returns a Promise

Async function vs a function that returns a Promise (2018):

Async function vs a function returns a Promise

There is a small, but quite important difference between a function that just returns a Promise, and a function that was declared with the async keyword.

Take a look at the following snippet:

function fn(obj) {
    const someProp = obj.someProp
    return Promise.resolve(someProp)
}

async function asyncFn(obj) {
    const someProp = obj.someProp
    return Promise.resolve(someProp)
}

asyncFn().catch(err =&gt; console.error('Catched')) // =&gt; 'Catched'
fn().catch(err =&gt; console.error('Catched')) // =&gt; TypeError: Cannot read proper

JavaScript will make sure that the asnycFn will return with a Promise (either resolved or rejected) even if an error occurred in it, in our case calling our .catch() block.

However with the fn function the engine doesn’t yet know that the function will return a Promise and thus it will not call our catch() block.

Workers

A worker enables you to run some tasks in a separate thread. Workers are Web APIs that are provided by the browser and not part of the JavaScript language.

A challenge with multithreaded code is that two threads may access the same variables and causes an inconsistence issue.

Introducing workers: To avoid this on the web, your main code and your worker code never get direct access to each other’s variables. Workers and the main code run in completely separate worlds, and only interact by sending each other messages. In particular, this means that workers can’t access the DOM (the window, document, page elements, and so on).

There are three different sorts of workers:

  • Dedicated workers
  • Shared workers can be shared by several different scripts running in different windows.
  • Service workers act like proxy servers, caching resources so that web applications can work when the user is offline. They’re a key component of Progressive Web Apps.

The last two types of workers are not introduced here.

Web worker

An simplified example from Introducing workers that use a worker to generates prims.

generate.js:

// Listen for messages from the main thread.
// If the message command is "generate", call `generatePrimes()`
addEventListener('message', (message) =&gt; {
    if (message.data.command === 'generate') {
      generatePrimes(message.data.quota);
    }
});

// A synchronous function which generates primes (very inefficiently)
// of a count specified by quoto.
function generatePrimes(quota) {
    // Generate primes and put them into an array primes
    // const primes = [];
    // ...

    // When we have finished, send a message to the main thread,
    // including the number of primes we generated.
    postMessage(primes.length);
}

In the above code, we start listening a message from the main script. If a message is sent, in the handler we reads the data property which is a copy from the main script and call generatePrimes. When the prims are done, we use postMessage() – Web APIs and pass it the count of prims generated to send a message to the main script. Similar, the data we passes to postMessage is a copy from the worker to the main script.

main.js:

// Create a new worker, giving it the code in "generate.js"
const worker = new Worker('./generate.js');

worker.postMessage({
    command: 'generate',
    100,
});

// When the worker sends a message back to the main thread,
// print the number of primes that were generated,
// taken from the message data.
worker.addEventListener('message', (message) =&gt; {
    console.log(`Finished generating ${message.data} primes!`);
});

Reference

JavaScript Runtime and the event loop

  • MDN: The event loop

    > ### Stack
    >
    > Function calls form a stack of frames.
    >
    > Note that the arguments and local variables may continue to exist, as they are stored outside the stack — so they can be accessed by any nested functions long after their outer function has returned.
    >
    > ### [“Run-to-completion”](The event loop – JavaScript | MDN “Permalink to “Run-to-completion””)
    >
    > Each message is processed completely before any other message is processed.
    >
    > This offers some nice properties when reasoning about your program, including the fact that whenever a function runs, it cannot be preempted and will run entirely before any other code runs (and can modify data the function manipulates).
    >
    > ### Several runtimes communicating together
    >
    > A web worker or a cross-origin iframe has its own stack, heap, and message queue. Two distinct runtimes can only communicate through sending messages via the postMessage method. This method adds a message to the other runtime if the latter listens to message events.
    >
    > ### Never blocking
    >
    > A very interesting property of the event loop model is that JavaScript, unlike a lot of other languages, never blocks. Handling I/O is typically performed via events and callbacks, so when the application is waiting for an IndexedDB query to return or an XHR request to return, it can still process other things like user input.

  • MDN: Asynchronous

    > Introducing asynchronous JavaScript
    >
    > In this article, we’ll learn about synchronous and asynchronous programming, why we often need to use asynchronous techniques, and the problems related to the way asynchronous functions have historically been implemented in JavaScript.
    >
    > How to use promises
    >
    > Here we’ll introduce promises and show how to use promise-based APIs. We’ll also introduce the async and await keywords.
    >
    > Implementing a promise-based API
    >
    > This article will outline how to implement your own promise-based API.
    >
    > Introducing workers
    >
    > Workers enable you to run certain tasks in a separate thread to keep your main code responsive. In this article, we’ll rewrite a long-running synchronous function to use a worker.

  • MDN: In depth: Microtasks and the JavaScript runtime environment

    > Starting with the addition of timeouts and intervals as part of the Web API (setTimeout() and setInterval()), the JavaScript environment provided by Web browsers has gradually advanced to include powerful features that enable scheduling of tasks, multi-threaded application development, and so forth. To understand where queueMicrotask() comes into play here, it’s helpful to understand how the JavaScript runtime operates when scheduling and running code.
    >
    > ### JavaScript execution contexts
    >
    > When a fragment of JavaScript code runs, it runs inside an execution context.
    >
    > There are three types of code that create a new execution context:
    >
    > – The global context is the execution context created to run the main body of your code; that is, any code that exists outside of a JavaScript function.
    > – Each function is run within its own execution context. This is frequently referred to as a “local context.”
    > – Using the ill-advised eval() function also creates a new execution context.
    >
    > As one code segment begins execution, a new context is constructed and is placed on the execution context stack. When it exits, the context is removed from the context stack.
    >
    > Using execution contexts in this manner, each program and function is able to have its own set of variables and other objects.
    >
    > ### Event loops
    >
    > Each agent is driven by an event loop, which collects any user and other events, enqueuing tasks to handle each callback. It then runs any pending JavaScript tasks, then any pending microtasks, then performs any needed rendering and painting before looping again to check for pending tasks.
    >
    > ### Task queue vs microtask queue
    >
    > The difference between the task queue and the microtask queue is simple but very important:
    >
    > – When executing tasks from the task queue, the runtime executes each task that is in the queue at the moment a new iteration of the event loop begins. Tasks added to the queue after the iteration begins will not run until the next iteration.
    > – Each time a task exits, and the execution context stack is empty, each microtask in the microtask queue is executed, one after another. The difference is that execution of microtasks continues until the queue is empty—even if new ones are scheduled in the interim. In other words, microtasks can enqueue new microtasks and those new microtasks will execute before the next task begins to run, and before the end of the current event loop iteration.

  • MDN: Using microtasks

  • The Node.js Event Loop, Timers, and process.nextTick()

    > ## What is the Event Loop?
    >
    > The event loop is what allows Node.js to perform non-blocking I/O operations — despite the fact that JavaScript is single-threaded — by offloading operations to the system kernel whenever possible.
    >
    > ## Event Loop Explained
    >
    > When Node.js starts, it initializes the event loop, processes the provided input script (or drops into the REPL, which is not covered in this document) which may make async API calls, schedule timers, or call process.nextTick(), then begins processing the event loop.
    >
    > The following diagram shows a simplified overview of the event loop’s order of operations.
    >
    > sql
    &gt; ┌───────────────────────────┐
    &gt; ┌─&gt;│ timers │
    &gt; │ └─────────────┬─────────────┘
    &gt; │ ┌─────────────┴─────────────┐
    &gt; │ │ pending callbacks │
    &gt; │ └─────────────┬─────────────┘
    &gt; │ ┌─────────────┴─────────────┐
    &gt; │ │ idle, prepare │
    &gt; │ └─────────────┬─────────────┘ ┌───────────────┐
    &gt; │ ┌─────────────┴─────────────┐ │ incoming: │
    &gt; │ │ poll │ │ └─────────────┬─────────────┘ │ data, etc. │
    &gt; │ ┌─────────────┴─────────────┐ └───────────────┘
    &gt; │ │ check │
    &gt; │ └─────────────┬─────────────┘
    &gt; │ ┌─────────────┴─────────────┐
    &gt; └──┤ close callbacks │
    &gt; └───────────────────────────┘
    &gt;

Workers

Web API

  • How does the Event Loop works in JavaScript? 2021.03

    Much its content is like a text version of a video: Philip Roberts: Help, I’m stuck in an event-loop. 2014.05.25. The author of the video Philip Roberts created a website Loupe to show how the event loop works for a given piece of code.

    > ### The Web API
    >
    > This is where code that isn’t handled by the V8 engine is executed to not “block” the main execution thread. When the Call Stack encounters a web API function, the process is immediately handed over to the Web API, where it is being executed and freeing the Call Stack to perform other operations during its execution.

  • Client-side web APIs

    > ### APIs in client-side JavaScript
    >
    > Client-side JavaScript, in particular, has many APIs available to it — these are not part of the JavaScript language itself, rather they are built on top of the core JavaScript language, providing you with extra superpowers to use in your JavaScript code. They generally fall into two categories:
    >
    > – Browser APIs are built into your web browser and are able to expose data from the browser and surrounding computer environment and do useful complex things with it.
    >
    > – Third-party APIs are not built into the browser by default.  For example, the Twitter API allows you to do things like displaying your latest tweets on your website.

  • Web APIs  a list of all the APIs and interfaces (object types).

Async funciton