Understanding how the Event Loop Works
Introduction
Have you ever heard somebody saying that slow code is ‘blocking’? Or perhaps you’ve seen Call Stack Limit Exceeded
errors when you’ve been coding? What does it all mean?
Well, it comes down to something called the Event Loop which is how your code is handled by the Javascript Runtime and its environment. It actually makes a lot of sense, and is useful to know - so I’ve tried to document my own learning here.
I recommend checking out Philip Roberts’ excellent talk if you’d like more detail. It provided the inspiration for this post.
How do things fit together?
If we look at the Javascript Runtime itself (V8 in Chrome for example), you see a heap (where memory allocation happens), and the call stack.
You also have Web APIs which the browser provides - things like the DOM, fetch, setTimeout etc. Then you also have the callback queue.
What is the stack?
Javascript has a single-threaded runtime which means it has a single call stack. That means technically it can only run one thing at a time.
For example:
function sayHello(x) {
return `Hi ${x}, how are you today?`
}
function greet(x) {
return sayHello(x)
}
function greetMe(x) {
var greeting = greet(x)
console.log(greeting)
}
greetMe("Ruairidh")
What does this code do? Well you can see multiple functions, which call each other.
Our stack looks something like this:
sayHello(x)
greet(x)
greetMe("Ruairidh")
main()
Blocking
What happens when things are slow? Things which are slow (image processing, network requests, etc) and are on the stack can be referred to as blocking.
If you had three synchronous requests in a row then you would need to wait for them to complete before anything else on the stack happens. This is a problem in browsers, as you can’t do anything else until those requests are complete. This would create a horrible user experience.
So what’s the solution? Well, asynchronous callbacks. They allow you to run code and give it a callback (or function) which we can execute later. But what happens to them? This is where the event loop comes in.
The Event Loop
Javascript as a runtime can only do one thing at a time, but we can do things concurrently thanks to APIs we can access in places such as the browser. Node does effectively the same thing.
When you call an asynchronous function, the relevant API picks it up and the function is popped off the stack. It’s being handled elsewhere, and the stack moves on to the next item.
When your asynchronous function is completed, it pushes your code onto the task queue. The event loop then checks the stack and the task queue. If the stack is empty, then it goes to the task queue and pushes the first item there onto the stack.
The Javascript Runtime then executes the code on the stack, and you receive the output.
This is why the hack of using setTimeout
and setting the value to 0 existed. It was a way of deferring something until the stack is clear. You can now use Promises to achieve the same result but in a more efficient way.
Promises have higher priority in the event loop stack as they create a ‘micro task’ and a setTimeout
creates a ‘macro task’ which also has a minimum delay of 4ms. Also, setTimeout
doesn’t provide a guaranteed time of execution, it provides a minimum time to execution. There’s a great post here if you’d like to learn more.
If you’d like a way of visualising this process, check out Philip Roberts’ Loupe tool.
Conclusion
The Javascript Event Loop is actually one of the simplest parts of a more complex process. It allows you to write non-blocking code in a single-threaded language and defer slower tasks to the queue while the rest of your code runs.
This means that you can develop more responsive and more efficient code and not have to wait on larger operations.