JS setTimeout explained

December 22, 2018

JavascriptEvent Loop

Has anyone ever asked you what will be the output if you execute below function?

function main(){
   console.log('First');
   setTimeout(function callback() {
      console.log('Second');
   }, 0); //timer set to 0
   console.log('Third');
}

Nobody asked you that? Ever? Hmm. Well if I were to ask you that question now, would you wonder why in the world am I using 0ms timer? I, for one, went crazy when I saw this kind of code for the first time. And to my great surprise the output of the above code was:

First
Third
Second

That’s absurd, isn’t it? Is Javascript broken? We instructed Javascript to execute the code after 0ms but it waited little longer (until after it executed line 6) to print ‘Second’. In my world 0ms means immediately, but maybe I’m crazy?

In this post I want to find out why did Javascript did what it did.

Hint: Javascript is not broken!


setTimeout is one of the most widely used (and often times misused) yet mostly widely misunderstood function in Javascript. In order to understand how setTimeout works we first need to get a sneak peek into the guts of the Javascript engine and how it operates.

Before we start, if you are not familiar with or don’t remember stack and queue data structure and what is a call stack, please pause right here and read about them first. Not the Big O stuff, you know, but just the basics about them.

⚡ There are many nuances and details of the internals of the engine that I have oversimplified below to make it easier for everyone to understand the fundamentals.

So let’s get started shall we?

The main thread

I am sure you have heard plenty of times people say – “Javascript is single-threaded”. What do they mean by that? What they basically mean by that is the browser has only one thread of execution that will execute every line of Javascript code that you write plus other tasks (any unit of work) that browser performs related to the UI like updates, user interactions etc. Unlike other programming languages like Java, where you can execute things concurrently with several threads, Javascript engine can execute only one line of code at any given time. Yes only one line of code at any given time.

⚡ There are other threads running in the JS environment. But let’s completely ignore that for now because that’s only confusing and irrelevant for this discussion.

That also means it has one call stack. When the engine runs a task on the main thread it adds a call stack. But browser needs to execute many tasks and until the call stack for current task is empty, it cannot proceed to another task (Here task mean any small unit of work like function call, event handler callback, user interactions etc.)

Queue up the tasks

What happens to those other tasks when the call stack is not empty? Well, those tasks have to wait somewhere. How about a queue? Yes the engine maintains a queue of tasks. Basically any tasks that the engine need to execute goes to a queue and pushed to the main thread in a first come first serve basis, as and when the call stack in the main thread becomes empty.

Welcome to the Loop

But who is responsible for pushing the task to the main thread? Javascript engine has something called event loop. The job of the event loop is to check if the call stack on the main thread is empty. If it is empty then it picks the task from the task queue and hands it over to the main thread for execution. As the name suggests, this runs in a loop – checking continuously if the call stack is empty at any given time and if it is empty handing over the tasks from the queue.

Finally, setTimeout(callback, time)

Now let’s go back to the original example we started with and see what really happens when we execute that function.

function main(){
   console.log('First');
   setTimeout(function callback() {
      console.log('Second');
   }, 0); //timer set to 0
   console.log('Third');
}

The above function is added to the call stack. Then it executes line 2 and prints ‘First’. It goes to line 3 and executes setTimeout function. Then it goes to line 6 and prints ‘Third’. But wait a second, what happens when setTimeout is executed? The main thread hands the callback function provided inside the setTimeout to a timer and moves on to the next line which is line 6 in this case (not 4 or 5 because 4 and 5 are part of the setTimeout function). The job of the timer is to keep track if specified time has elapsed, surprise surprise. After the specified time has elapsed, the timer cannot execute the callback function itself. Remember any function execution can only happen on the main thread of execution. Also timer can’t just push things to the main thread to execute (nobody can do that except the event loop). Instead what the timer can do is push the callback function to the task queue (and remember on a queue, things can only be added to the end). Since the event loop will be continuously running, tasks in the task queues will be pushed to the main thread as and when the call stack becomes empty in the main thread. After every “tasks” in front of the above callback is executed, the event loop pushes the callback function to the main thread and it will be executed as well.

Event Loop

One more example

Now that we have looked at how setTimeout really works, let’s go through one more example to solidify our understanding. What would be the output if we execute below code?

function main(){
   console.log('First');
   setTimeout(function callback(){
      console.log('Second');
   }, 0);
   //this function runs synchronously for 30 seconds
   sycnFunctionThatRunsFor30Sec();
   console.log('Third');
}

Well the output would exactly be the same as the above example we looked at. But how long after we execute this function will it print ‘Second’? Immediately or after 30 seconds? That’s right. It will print ‘Second’ after 30 seconds. It’s not very intuitive is it? We told the engine to execute the callback function after 0ms and it completely ignored that and executed after 30 seconds. Why? Because remember how the engine works. When the engine executes the code, the main function will be added to the call stack. It executes the line 2 and prints ‘First’. It then executes line 3 which just kicks off the timer of 0ms. The timer is not allowed to execute the callback itself. The timer immediately completes and it adds the callback function to the task queue. But the call stack is still not empty. It executes line 7. It blocks the main thread and runs for 30 seconds. It then executes line 8 and prints ‘Third’. Now it reaches the end of main function and the call stack is empty. Event loop finds out the call stack is empty so it checks the queue and finds the callback function that the timer had added earlier. It pushes the callback function to the main thread, it executes and prints ‘Second’.

Congratulations! Next time you see setTimeout function with some weird 0ms timer that doesn’t execute immediately you know exactly why.


tyroprogrammer

This is a blog post by tyroprogrammer.