Event loop

Tasks

The JavaScript engine initially runs the available code of an application right after the application is opened. Then the JavaScript engine waits quietly for new code to be executed. In this context we call these new chunks of code to be executed tasks or macrotasks. These tasks can be executing an event handler, initiated by an event (also a task) such as a mouse click, run an externally fetched script, executing a callback function after a timeout being fired, etc. When a new task is served while the engine is busy, the task is queued in a (macro)task queue, scheduling it to be run its turn.

The procedure of handling tasks is described as an event loop:

  1. While there are tasks in the task queue to finish:
  2. Wait idly until a new task is served. Then:
    • Enqueue the new task to the task queue.
    • Repeat the procedure.

Microtasks

Promise event handlers are not considered (macro)tasks. They are considered microtasks and handled in a separate way within the event loop. After a promise resolved (or rejected), the associated event handler is enqueued in a microtask queue, separate from the task queue. In that microtask queue it waits for execution until the engine finished its current task. Immediately after every task in the task queue, the engine executes all possible tasks from the microtask queue, before continuing the event loop.

Consider the next example:


setTimeout(() => console.log("First log in the code"));

new Promise((resolve, reject) => { resolve() })
  .then(() => console.log("Second log in the code"));

console.log("Third log in the code");

// logs:
// "Third log in the code"
// "Second log in the code"
// "First log in the code"

This example will be handled in the event loop as follows:

  1. The first task is to execute this code. It enqueues the timeout's callback in the task queue, it enqueues the promise's event handler in the microtask queue and then executes the log to the console in the last line, logging "Third log in the code". Then this task is finished.
  2. There is a microtask in the microtask queue, so it pauses the main event loop and first executes all microtasks in the microtask queue: it executes the promise's event handler, resulting in the log "Second log in the code".
  3. The microtask queue is now empty, so it continues the main event loop by going to the next task in the task queue, which is executing the timeout's callback, resulting in the log "First log in the code". Then this task is finished.
  4. Both the microtask queue and task queue are now empty. The event loop waits idly for new tasks...

An other example:


(async function myExample() {
  try {
    const resolve1 = await Promise.resolve("handler1");
    console.log(resolve1);

    setTimeout(async () => {
     const resolve2 = await Promise.resolve("handler2");
     console.log(resolve2);
    }, 2000);	
	
    const resolve3 = await Promise.resolve("handler3");
    console.log(resolve3);	
  }
  catch (error) {
    console.error(error);
  }
})();

// logs:
// "handler1"
// "handler3"
// "handler2"

This example will be handled in the event loop as follows:

  1. The first task is to execute the async function. It enqueues the logs of resolve1 and resolve3 (both promise event handlers) in the microtask queue. It executes the timeout. After 2 seconds the timeout triggers and the callback function is enqueued in the task queue. Then this task is finished.
  2. There are microtasks in the microtask queue, so it pauses the main event loop and first executes all microtasks in the microtask queue: it successively logs "handler1" and "handler3".
  3. The microtask queue is now empty, so it continues the main event loop by going to the next task in the task queue, which is executing the timeout's callback. This callback enqueues the log of resolve2 (a promise event handler) in the microtask queue. Then this task is finished.
  4. There are microtasks in the microtask queue, so it pauses the main event loop and first executes all microtasks in the microtask queue: it logs "handler2".
  5. Both the microtask queue and task queue are now empty. The event loop waits idly for new tasks...