Event Loop
Let’s make a (perhaps shocking) claim: despite clearly allowing asynchronous JS code (like the timeout we just looked at), up until recently (ES6), JavaScript itself has actually never had any direct notion of asynchrony built into it.
What!? That seems like a crazy claim, right? In fact, it’s quite true. The JS engine itself has never done anything more than execute a single chunk of your program at any given moment, when asked to.
“Asked to.” By whom? That’s the important part!
The JS engine doesn’t run in isolation. It runs inside a hosting environment, which is for most developers the typical web browser. Over the last several years (but by no means exclusively), JS has expanded beyond the browser into other environments, such as servers, via things like Node.js. In fact, JavaScript gets embedded into all kinds of devices these days, from robots to lightbulbs.
But the one common “thread” (that’s a not-so-subtle asynchronous joke, for what it’s worth) of all these environments is that they have a mechanism in them that handles executing multiple chunks of your program over time, at each moment invoking the JS engine, called the “event loop.”
In other words, the JS engine has had no innate sense of time, but has instead been an on-demand execution environment for any arbitrary snippet of JS. It’s the surrounding environment that has always scheduled “events” (JS code executions).
So, for example, when your JS program makes an Ajax request to fetch some data from a server, you set up the “response” code in a function (commonly called a “callback”), and the JS engine tells the hosting environment, “Hey, I’m going to suspend execution for now, but whenever you finish with that network request, and you have some data, please call this function back.”
The browser is then set up to listen for the response from the network, and when it has something to give you, it schedules the callback function to be executed by inserting it into the event loop.
So what is the event loop?
Let’s conceptualize it first through some fake-ish code:
// `eventLoop` is an array that acts as a queue (first-in, first-out)
var eventLoop = [ ];
var event;
// keep going "forever"
while (true) {
// perform a "tick"
if (eventLoop.length > 0) {
// get the next event in the queue
event = eventLoop.shift();
// now, execute the next event
try {
event();
}
catch (err) {
reportError(err);
}
}
}
This is, of course, vastly simplified pseudocode to illustrate the concepts. But it should be enough to help get a better understanding.
As you can see, there’s a continuously running loop represented by the while
loop, and each iteration of this loop is called a “tick.” For each tick, if an event is waiting on the queue, it’s taken off and executed. These events are your function callbacks.
It’s important to note that setTimeout(..)
doesn’t put your callback on the event loop queue. What it does is set up a timer; when the timer expires, the environment places your callback into the event loop, such that some future tick will pick it up and execute it.
What if there are already 20 items in the event loop at that moment? Your callback waits. It gets in line behind the others — there’s not normally a path for preempting the queue and skipping ahead in line. This explains why setTimeout(..)
timers may not fire with perfect temporal accuracy. You’re guaranteed (roughly speaking) that your callback won’t fire before the time interval you specify, but it can happen at or after that time, depending on the state of the event queue.
So, in other words, your program is generally broken up into lots of small chunks, which happen one after the other in the event loop queue. And technically, other events not related directly to your program can be interleaved within the queue as well.
Note: We mentioned “up until recently” in relation to ES6 changing the nature of where the event loop queue is managed. It’s mostly a formal technicality, but ES6 now specifies how the event loop works, which means technically it’s within the purview of the JS engine, rather than just the hosting environment. One main reason for this change is the introduction of ES6 Promises, which we’ll discuss in Chapter 3, because they require the ability to have direct, fine-grained control over scheduling operations on the event loop queue (see the discussion of setTimeout(..0)
in the “Cooperation” section).