Made in Builder.io

Upcoming webinar with Figma: Design to Code in 80% Less Time

Announcing Visual Copilot - Figma to production in half the time

Builder.io logo
Talk to Us
Platform
Developers
Talk to Us

Blog

Home

Resources

Blog

Forum

Github

Login

Signup

×

Visual CMS

Drag-and-drop visual editor and headless CMS for any tech stack

Theme Studio for Shopify

Build and optimize your Shopify-hosted storefront, no coding required

Resources

Blog

Get StartedLogin

‹ Back to blog

Web Development

Visualizing The Timer Queue in Node.js Event Loop

April 3, 2023

Written By Vishwas Gopinath

Welcome to the third article in our series on visualizing the Node.js event loop. In the previous article, we explored Microtask Queues and their order of priority when executing asynchronous code. In this article, we will discuss the Timer Queue, which is another queue in Node.js used to handle asynchronous code.

Before we dive into the Timer Queue, let's quickly recap Microtask Queues. To queue a callback function into the Microtask Queue, we use functions such as process.nextTick() and Promise.resolve(). Microtask Queue has the highest priority when it comes to executing asynchronous code in Node.js.

Enqueueing callback functions

Now let's move on to the Timer Queue. To queue a callback function into the Timer Queue, we can use functions such as setTimeout and setInterval. For the purpose of this blog post, we will be using setTimeout.

To understand the order of execution in the Timer Queue, let's conduct a series of experiments. We will queue up tasks in both the Microtask Queue and the Timer Queue.

// index.js
setTimeout(() => console.log("this is setTimeout 1"), 0);
setTimeout(() => console.log("this is setTimeout 2"), 0);
setTimeout(() => console.log("this is setTimeout 3"), 0);

process.nextTick(() => console.log("this is process.nextTick 1"));
process.nextTick(() => {
  console.log("this is process.nextTick 2");
  process.nextTick(() =>
    console.log("this is the inner next tick inside next tick")
  );
});
process.nextTick(() => console.log("this is process.nextTick 3"));

Promise.resolve().then(() => console.log("this is Promise.resolve 1"));
Promise.resolve().then(() => {
  console.log("this is Promise.resolve 2");
  process.nextTick(() =>
    console.log("this is the inner next tick inside Promise then block")
  );
});
Promise.resolve().then(() => console.log("this is Promise.resolve 3"));

The code contains three calls to process.nextTick() and three calls to Promise.resolve() and three calls to setTimeout. Each callback function logs an appropriate message. All three setTimeout calls have a delay of 0ms, which means that the callback functions are queued up as soon as each setTimeout statement is executed on the call stack. The second process.nextTick(), and the second Promise.resolve() have an additional process.nextTick() statement, each with a callback function.

When the call stack executes all statements, we end up with three callbacks in the nextTick queue, three in the Promise queue, and three in the Timer Queue. There is no further code to execute, and control enters the event loop.

The nextTick queue has the highest priority, followed by the Promise queue, and then the Timer Queue. The first callback from the nextTick queue is dequeued and executed, logging a message to the console. Then, the second callback is dequeued and executed, which also logs a message. The second callback includes a call to process.nextTick(), which adds a new callback to the nextTick queue. Execution continues, and the third callback is dequeued and executed, logging a message as well. Finally, the newly added callback is dequeued and executed on the call stack, resulting in the fourth log message in the console.

After the nextTick queue is empty, the event loop proceeds to the Promise queue. The first callback is dequeued and executed on the call stack, printing a message in the console. The second callback has a similar effect and also adds a callback to the nextTick queue. The third callback in the Promise is executed, resulting in the next log message. At this point, the Promise queue is empty, and the event loop checks the nextTick queue for new callbacks. It finds one, which is also executed by logging a message to the console.

Now, both Microtask Queues are empty, and the event loop moves on to the Timer Queue. We have three callbacks, and each of them is dequeued and executed on the call stack one by one. This will print "setTimeout 1", "setTimeout 2", and "setTimeout 3".

Callbacks in the Microtask Queues are executed before callbacks in the Timer Queue.

Alright, so far, the order of priority is the nextTick queue, followed by the Promise queue, and then the timer queue. Let's now proceed to the next experiment.

// index.js
setTimeout(() => console.log("this is setTimeout 1"), 0);
setTimeout(() => {
  console.log("this is setTimeout 2");
  process.nextTick(() =>
    console.log("this is inner nextTick inside setTimeout")
  );
}, 0);
setTimeout(() => console.log("this is setTimeout 3"), 0);

process.nextTick(() => console.log("this is process.nextTick 1"));
process.nextTick(() => {
  console.log("this is process.nextTick 2");
  process.nextTick(() =>
    console.log("this is the inner next tick inside next tick")
  );
});
process.nextTick(() => console.log("this is process.nextTick 3"));

Promise.resolve().then(() => console.log("this is Promise.resolve 1"));
Promise.resolve().then(() => {
  console.log("this is Promise.resolve 2");
  process.nextTick(() =>
    console.log("this is the inner next tick inside Promise then block")
  );
});
Promise.resolve().then(() => console.log("this is Promise.resolve 3"));

The code for the fourth experiment will be mostly the same as that of the third, with one exception. The callback function passed to the second setTimeout function now includes a call to process.nextTick().

Let's apply what we learned from the previous experiment and fast forward to the point where callbacks in the microtask queues have already been executed. Assume we have three callbacks queued up in the timer queue. The first callback is dequeued and executed on the call stack, resulting in a "setTimeout 1" message being printed to the console. The event loop proceeds and runs the second callback as well, resulting in a "setTimeout 2" message being printed to the console. However, this also queues up a callback function in the nextTick queue.

After executing each callback in the timer queue, the event loop goes back and checks the microtask queues. It checks the nextTick queue and identifies a callback that needs to be executed. This callback is dequeued and executed on the call stack, resulting in the "inner nextTick" message being printed to the console.

Now that the microtask queues are empty, the control goes back to the timer queue, and the last callback is executed resulting in a "setTimeout 3" message in the console.

Callbacks in microtask queues are executed in between the execution of callbacks in the timer queue

// index.js
setTimeout(() => console.log("this is setTimeout 1"), 1000);
setTimeout(() => console.log("this is setTimeout 2"), 500);
setTimeout(() => console.log("this is setTimeout 3"), 0);

The code contains three setTimeout statements that queue up three different callback functions. The first setTimeout has a delay of 1000ms, the second has a delay of 500ms, and the third has a delay of 0ms. The callback functions simply log a message to the console when they are executed.

We will skip visualization for this experiment since the execution of the code snippet is quite simple. When multiple setTimeout calls are made, the event loop queues up the one with the shortest delay first and executes it before the others. As a result, we observe “setTimeout 3” executing first, followed by “setTimeout 2”, and then “setTimeout 1”.

Timer queue callbacks are executed in a first-in, first-out (FIFO) order.

The experiments show that callbacks in the Microtask Queue have a higher priority than those in the Timer Queue, and callbacks in the Microtask Queue are executed in between the execution of callbacks in the Timer Queue. The Timer Queue follows a first-in, first-out (FIFO) order.

Introducing Visual Copilot: convert Figma designs to code using your existing components in a single click.

Try Visual Copilot

Share

Twitter
LinkedIn
Facebook
Hand written text that says "A drag and drop headless CMS?"

Introducing Visual Copilot:

A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot
Newsletter

Like our content?

Join Our Newsletter

Continue Reading
Company News3 MIN
Builder.io closes $20 million in funding led by M12, Microsoft’s Venture Fund
WRITTEN BYSteve Sewell
April 24, 2024
AI9 MIN
How to Build AI Products That Don’t Flop
WRITTEN BYSteve Sewell
April 18, 2024
Web Development13 MIN
Convert Figma to Code with AI
WRITTEN BYVishwas Gopinath
April 18, 2024