setTimeout, setImmediate, and Promises in an event loop?

Rahul Bharati
6 min readAug 9, 2022

For all the Javascript developers out there, this is one of the most confusing questions we have faced. How does setTimeout with 0 delays and setImmediate is invoked? Do they get executed at the same time or is there something different? Even so, how do Promises fit in all this? In this post, we are going to take a look at how these functions are executed.

What is an event loop?

Before we move on to the code and answer, let’s take a quick look at the event loop. As we all know event loop is a loop that keeps track of all incoming events and processes and sends feedback to the node process. But is this all the event loop does? Short answer, No.

Basically, an event loop is a process that is tasked to keep track of all incoming events, callback stack, and task queues and process requests accordingly. Since the Node process is single-threaded it doesn’t mean that the Node application is also single-thread. Nodejs relies on the v8 engine to bind Javascript variables and function to C++ types and function which is then executed in the C++ environment through the libuv library.

Now to sum it up, when the request is made the function gets on the callback stack where it is checked for asynchronicity if it requires time then that function is sent to the C++ threads handled by libuv to process it in the background. Once processed the function callback is queued to task queues where it waits till the time callback stack is empty. Once the callback stack is empty, the event loop picks tasks from the task queue and loads them onto the callback stack where it is executed finally.

This is a basic overview of the event loop. I’ll write another post just dedicated to the event loop. Let’s move on to the main topic of this post.

What is setTimeout?

According to the definition, The global setTimeout() method sets a timer that executes a function or specified piece of code once the timer expires. The setTimeout() method takes two arguments i.e. callback and delay in ms. A simple setTimeout will look the following.

What is setImmediate?

There is no specific definition but to give an idea, A setImmediate() method is a timer function that needs to be executed as soon as possible. But this function will be executed immediately only after the current event loop is finished. The setImmediate() method takes one argument i.e. callback. A setImmediate will look like the following. This should not be used on the web since there isn’t proper support for this and it may break on browsers not running on the Javascript V8 engine.

What is a Promise?

A promise is basically a proxy object that is created when the outcome of an asynchronous operation is unknown. A promise can be resolved or rejected depending upon the operation. All I/O tasks that are non-blocking are based on promises. To create a new Promise following syntax is used.

Now that we have seen setTimeout, setImmediate, and Promise, let’s try to figure out how it all fits in the event loop.

For this purpose, we’ll use the following code block.

In the code block, we are basically setting two setTimeout that will execute after 1000ms and 0ms. One setImmediate that should execute immediately, and two promise, one with the delay because of a for-loop and one without delay. If we run the following code block, here is the output for the same.

Let’s break down how each block of code/function was executed.

  • First console.log() on line number 8 was pushed to call stack, since it doesn’t require any time to process, it got executed immediately and we get first as a log
  • Then setTimeout function on line number 10 is executed. It gets pushed on the call stack and sets up a timer for 1000ms (1s) and sends the callback to the libuv thread to handle the timeout. As soon as the timer is set it gets popped from the call stack hence nothing is printed and the callback is queued.
  • Here setImmediate function on line number 14 is executed. This function gets pushed onto the call stack and a callback is set up to execute immediately. Once the callback is set up this function gets popped from the call stack and the new callback is queued hence nothing is printed.
  • On line number 18, happens the same thing as the first timeout only this time, this callback is set to be executed after 0ms. Once the callback is set up, this function gets popped from the call stack and nothing prints.
  • The Promise function on line number 22 gets pushed on the call stack, and a new micro-task is set up, this microtask is executed on a separate thread and the callback is returned and it gets queued. Once the task is set up, this function is popped from the call stack and nothing happens.
  • The Promise function on line 26 gets pushed onto the call stack, and new micro-task is setup, this micro-task is executed on a separate thread and the callback is returned and it gets queued. Once the task is set up, this function is popped from the call stack and nothing happens.
  • Then we move to the last line which is console.log(), this function is pushed on to call stack and executed immediately since it doesn’t require any other execution, and hence seven is printed.
  • At this point, we have 5tasks queued i.e two setTimeouts with 1000ms and 0ms delay. One setImmediate and 2 promises. Now we have to see how these are resolved.
  • As you may notice Promises are resolved first, is it because they are taking less time? No exactly, the promise callbacks are more of a microtask that gets priority in the task queue hence they are placed first in the task as soon as the process is done, hence fifth and sixth are printed respectively.
  • Then the first setTimeout is executed then setImmediate, what happened here? Most of the time we think setImmediate is to execute first because as per definition it is to execute as soon as possible, but for setImmediate to be executed it needs the task queue empty at the beginning of the next event loop, not the current one. Since the setTimeout was queued it got executed, first then setImmediate
  • Finally, last but not least our delayed setTimeout ran because it got resolved in the next loop hence after setImmediate.

One thing to keep in mind is that the promises are not exactly placed on the task queues but they are placed on the different stacks which will block the execution till all the micro-tasks aka promise callbacks are done. That means if you have a promise chained and they get executed at the same time, then your promise callbacks will run first then other functions this could sometime lead to an unexpected delay in setTimeout functions.

Thanks for reading this, I’m still processing the event loop and there is so much more to it. Nodejs experts are welcome to audit and make this more helpful to the developers in understanding how these timer functions and promises work together.

--

--

I’m a full-stack developer (MERN). I also like to read books, play guitar, and sketch.