Understanding JavaScript: Event loops and timers

In this Writer’s Room blog – articles written by Andela Community members – Ebenezer Adjei explores the world of JavaScript, namely how to use event loops and timers internally!

Have you wondered how JavaScript – a single-threaded programming language – achieves asynchronous processing? The secret is the event loop! No matter how hard you try, JavaScript as a language can do just one thing at any point in time. Don’t get it confused with your multi-core processor. This, however, does not mean that JavaScript will wait for your api call that takes 30 seconds to complete before running the next line of code.

JavaScript executes our code from the top down and pushes events in and out of the call stack with the help of the event loop. To make web browsers perform more efficiently, they have their implementations in addition to the JavaScript engine. For example, setTimeout , setInterval , fetch api, and DOM events are browser implementations (web apis) and not a part of the JavaScript engine.

The Event Loop
The event loop is a crucial aspect of JavaScript’s runtime model, which is responsible for executing code, collecting, and processing events. It helps to execute code asynchronously (non-blocking). It’s very important in browsers and the server side (Node.js). It’s based on the event-centered programming model, where the execution of programs is founded on how events occur but not how the code is written. Imagine a user reading a file from the computer; the program does not hang until the file reading is done. The operating system reads the file and sends an event to the queue when the file reading is done. By doing so, the program can respond to other I/O operations from the user.

The event loop got its name from how it is implemented; it continues to wait for a message to arrive and then processes the message. Messages added to the event loop follow the queue data structure, which is First In First Out (FIFO). In this case, new messages are added to the bottom of the queue, so that those that entered the event loop first can be processed first.

Have you forgotten how a queue works? Just imagine yourself in a queue at the bank. You’ll get served last if you join the queue last. The event loop works the same way.

In the event loop, each message is processed completely before any other message is processed. This means that once a message is processed, it can’t be paused for another message to be processed. This makes it easy to follow along with the execution of the program. However, a negative implication of this model is that when a message takes too long to complete, it renders the web application redundant (e.g. inability to click or scroll).

Using the event loop means that several events can be added to the queue, and they will be processed in the manner in which they were added to the queue.

Have you thought about how new messages are added to the event loop?

In web browsers, new messages are added based on events that occur. When an event is created, and the event has a listener attached to it, then a new message is added to the event loop. On the other hand, if there is no listener, the event is lost. For example, when a mouse hovers over an element and an event is attached, a new message is added for processing.


Runtime model

The image below demonstrates a theoretical model of the JavaScript runtime.

Stack: it is a portion of memory reserved for function calls. New functions are added to the stack. When a new function is called, a new stack frame is created. When a function is done processing, it leaves the stack.

Heap: It is a large region of memory reserved for allocating objects.

Queue: It contains messages that are waiting to be processed. When a message is done processing, it leaves the message queue. Each message in the queue is associated with a function that gets executed to handle the message.

At the server side, typically in node.js, the event loop allows the server to perform non-blocking I/O operations. Node.js uses the event loop to manage operations and dispatches them to the operating system kernel whenever possible. Once the operations reach the operating system, the system has its own way of completing the tasks.

Timing Events in JavaScript
Timers in JavaScript allow us to execute code at specified times. They include setTimeout , setInterval and setImmediate . The first two timers are standard and widely used in JavaScript, while the last one isn’t standard and is only implemented in a few browsers and Node.js.

setTimeout
The setTimeout function allows us to run some specified code after several milliseconds. It takes two arguments; the first argument is a function to run, and the second argument is the number of delays in milliseconds before the function will be pushed into the message queue. The second value is optional, and it defaults to 0. When setTimeout is called, with the second argument being 0, the function will be pushed into the message queue immediately. Otherwise, the delay would have to elapse before the function is pushed onto the message queue. The setTimeout function will be pushed onto the message queue immediately after the delay elapses, but it is not guaranteed to process immediately after the delay has elapsed. This is because there may be other functions already in the message queue. And since it’s a queue, the order is First In First Out (FIFO). Therefore, the delay that is set in setTimeout only represents the minimum time but not a guaranteed time.

From the example below, you’ll understand that the function passed to setTimeout is not guaranteed to run immediately after its time elapses.

SetTimeout doesn’t pause execution

The setTimeout function is asynchronous, meaning it does not pause the execution of other call stacks. While the setTimeout is waiting for the number of delays, other functions can execute without any interference. Consider the code below:

In the example above, the code isn’t executed in the order in which the functions were called. This is due to the way in which setTimeOut works internally. When the first function is called, the callback function isn’t pushed onto the event queue right away. Instead, it will wait for 5 seconds. While that is happening the second function is called, which in turn will also wait 3 seconds before it is pushed onto the event queue. While the second function is also waiting, the third function is called which waits for 1 second and is then pushed onto the event queue.


After 1 second, the 3rd function call is pushed onto the event queue.

After 3 seconds, the second function call is pushed onto the event queue.

After 5 seconds, the first function call is pushed onto the event queue.

The sequence of execution for the code above will look like this and they will be processed as and when they are pushed to the event queue.

setInterval

The setInterval() method, offered on the Window and Worker interfaces, repeatedly calls a function or executes a code snippet, with a fixed time delay between each call. (source mdn). The two methods – setTimeout and setInterval – are identical except that setTimeout runs the specified function once, but setInterval continues to run the specified function repeatedly at the set delay.

It is important to note that setTimeout and setInterval are not a part of the JavaScript specification. But environments that implement them have their own internal scheduler as in the case of browsers and Node.js. 

Terminate the execution of timers

Calling setTimeout or setInterval creates a function to be executed at a particular time. 

The function call can be terminated before execution or can be removed after execution.

When the function call is terminated before execution, the operation is removed from the event loop. It’s good practice to clearTimeout or clearInterval after the operation is complete. The poll of IDs returned by the two functions are shared so you can use clearTimeout and clearInterval interchangeably. However, to ensure readability and clarity, it’s best to avoid this interchange.

Example.

The code above will work fine since the timerID is shared with setTimeout and setInterval internally.

A call to create a timer returns an ID which you can use the cancel the execution of the timer.

Conclusion

Despite the advancement in computing and the advent of multi-core processors, web applications run on a single thread. This, however does not mean that our applications run in a blocking manner. It is important to note that JavaScript is never a blocker. Due to intelligent programming techniques, programs can run smoothly without blocking the execution of other events. When I/O is processing, or an XHR request is processing, other events can still be processed. There are legacy exceptions like “alert” and synchronous XHR which can block the execution of other events. However, it is a good practice to avoid them.

Want to be part of the Andela Community? Then join the Andela Talent Network!


If you found this blog useful, check out our other blog posts for more essential insights!

Related Posts