Whoop Whoop! Event Loop!

Last week a friend asked me, Is Node is multi-threaded? How would Node do asynchronous work if it’s not multi-threaded? Good question! I didn’t know the answer.

But as it turns out, Node (and Javascript in general) is single-threaded. This means there is only one process, one flow of control. Instead of having multiple threads to process simultaneous work, the Node runtime environment has an ecosystem of interconnected data structures that preserve a fast workflow. At the conceptual center of these structures is the event loop.

All modern Javascript engines rely on an event loop concurrency model, not just Node. So, browser-lovers, read on.

A stack, a queue, an event loop - oh my!

Image: Mozilla Developers Network (annotations mine)

This is a simplified diagram of a Javascript runtime. (I added the “Web APIs” box for browser-related completeness.)

Start with the stack. You’ve probably been exposed to the stack before, especially in the form of stack traces for Javascript errors. A stack trace, like the stack itself, is made of the series of functions and local variables that have been pushed onto the stack. Each of those functions and its local variables make up a frame in the call stack.

Functions are added to the stack from the main Javascript program – or, from the message queue. The queue contains callback functions that are ready to be, well, “called back.” In the browser, callbacks may be associated with DOM events like “click” or “hover”; or, for both backend and frontend Javascript, they may be associated with resolved Promises. In fact, they may contain responses from any number of Web APIs, like timer or the infamous XMLHttpRequest. (I didn’t realize those were separate APIs until I started learning about the event loop!) When a message is processed, its callback function gets called and thereby is added to the stack.

The event loop is the loop that processes messages in the queue such that the callback functions get pushed onto the stack.

For an animated demo of how the event loop works in the browser, check out Philip Roberts' 2014 JSConf talk.

Image: Philip Roberts, 2014

Much non-blocking

A very interesting property of the event loop model is that JavaScript, unlike a lot of other languages, never blocks. Handling I/O is typically performed via events and callbacks, so when the application is waiting for an IndexedDB query to return or an XHR request to return, it can still process other things like user input. – MDN

Thanks to the event loop model, Javascript is non-blocking. That means you don’t have to wait for slow tasks to complete before processing other tasks. We just keep pushing or popping frames on and off the stack; some of those function calls add work to someone else’s plate (by calling a web API or making HTTP requests, for example), outside of our single process; we receive callbacks in our queue when that external work is done; and whenever the stack has capacity, we pull in those callbacks.

So, even though Javascript is single-threaded, the event loop model allows it to be fast and non-blocking. We just keep grabbing one thing at a time, one thing at a time, one thing at a time, keep on going forever. We are a machine.