The JavaScript Event Loop
March 22, 2019
A wild JavaScript code snippet appears!
const log = (output) => { console.log(`--${output}--`); };
log("first");
setTimeout(function fifth() { log("fifth"); }, 500);
setTimeout(function third() { log("third"); }, 0);
setTimeout(function fourth() { log("fourth"); }, 0);
log("second");
For a boss-fight JavaScript code snippet that has more use cases, please check out this gist
Spoiler Alert / TL;DR
I have the order of execution given out as first
through fifth
for clarity
and to make it so readers can skim this and be on their way when they need a
reference.
Slightly More TL;DR
JavaScript will run a function until completion (ignoring iterators), so there is no JS-level function interleaving. This means that you will not run into the same race conditions you may hit in C, C++, or other languages with threading. The JS engine under the hood is indeed using threads and waiting for asynchronous actions concurrently; however, JS-level code will run top-down sequentially in the current stack frame.
Order Matters
JavaScript, as mentioned in previous blog posts (and above in the second TL;DR section), executes from top to bottom. That means that in our above snippet, every line is going to get executed before any other JavaScript in the same process is executed.
We’re All JavaScript Runtimes
So on line 1, we define a function that we assign to the
variable log
that adds some extra formatting to a standard console.log
call. Great! Now we can use that throughout our code.
So we proceed to line 3, which invokes this new function and passes along the
string first
to be output to the console. Because both log
and console.log
are
synchronous functions, meaning they do not escape the event loop, this code will
first evaluate log
, enter the function, evaluate console.log
which is
native code, and then return undefined
from both function because we do not
specify a return value.
The JS runtime does not move onto anything else during this time, it only evaluates this code. After it returns it is free to move on to line 5:
setTimeout(function fifth() { log("fifth"); }, 500);
This is where things start to break down the 4th wall and start reaching
into asynchronous territory. setTimeout
is a magical function (no, not
really) that sets a timer with the JS runtime. The JS runtime effectively sets
an eggtimer for 500
milliseconds, puts it down on a counter, and then returns
back to the calling function. Unlike my log
function, setTimeout
actually
does have a return value - an identifier of the timer it just set. It’s outside
of the scope of this demo, but if you wanted to cancel your timer, you could
run clearTimeout(idOfTimeout)
, and the callback you passed would never be
executed. But I digress.
Anyway, now we’ve returned from the first setTimeout
and proceed to the next.
It is largely the same as the last call, except for one notable difference:
setTimeout(function third() { log("third"); }, 0);
The timeout is set to 0
. What does your gut tell you will happen here? The
timeout is set to 0
milliseconds, so hypothetically that could mean run
right now
. However, that is not the case. setTimeout
sets a timer in the
background, and those timers cannot be executed until the current block of
JavaScript code has completed execution. So, the same metaphorical eggtimer
gets set, with a time of 0
. So even if the timer goes off by the time we
return from this timeout, our friendly runtime can’t do anything about it
because we are blocking the runloop with our pesky code. Well let’s carry on
then.
The next line is the same as the one before, I just wanted to make a point of it to show that these messages get stacked up on a queue and will get executed in the order they’re dropped into the timer. So in this situation, these functions will be run in their respective numerical order. Perfect.
Finally we run into our last line:
log("second");
This call says second
, but is the last line of the file. And it is even
correct! When this program runs, it will be the second line of code to write
text to the console because it is synchronous, like the first call to log
. So
it’s going to execute, write code to your console, return, and then this block
of JavaScript has completed execution!
The console output at this point will look like this:
--first--
--second--
If this was run with NodeJS, and there were no timers or other event listeners configured, your program would now exit because its work is done.
However, we setup three different timers, so its work is not yet complete.
Since there was a 0
millisecond delay (remembering from previous posts that
in JavaScript this means at least 0
, not exactly 0
) from our second and
third setTimeout
calls, our runtime can finally put those eggtimers away
and will run the functions we passed into those setTimeout
calls. Our output
will now look like this:
--first--
--second--
--third--
--fourth--
Our runtime is just going to hang out for a bit, waiting for something else to
happen. In this sitation, there is nothing else waiting to run except for tour
timer. So after a grueling 500
milliseconds (at least 500
from when it was
set, our final timer will go off, and the callback passed in will execute,
leaving us with the final console output:
--first--
--second--
--third--
--fourth--
--fifth--
At this point, the runtime’s work is done and as it bids you adieu the program will exit.
The More You Know
This is the basic formula for how asynchronous actions in the event loop functions. Network calls, file operations, event callbacks, etc. all behave similarly. They have functions implemented at the native level that allow access to a three underlying runtime’s thread pool and can dispatch actions outside of the main JS thread. When they call back, the JS runtime receives an event, and at the next tick, or iteration of the event loop, those events will have a chance to execute. I’ve included a link below to how NodeJS’s event loop runs, which covers the various phases it includes. Browser runtimes function similarly but may have different phases or timings.
Resources and Citations
There are many excellent resources for deep explanations of event loops. These two are my favorite: