Understanding eventloop, microtasks, webworkers for better web performance & UX

Chandu Ivaturi
6 min readSep 5, 2019

The Event Loop is one of the most important aspects to understand about JavaScript.

Webpage performance can be drastically improved if we understand what’s happening under the hood of the event loop mechanism.

This post aims to explain the event loop mechanism, how it can block I/O operations and user events while we do any complex operations in javascript.

Let’s just start with common truth “Javascript has a single-threaded mechanism”

This means that by design, JavaScript engines — originally browsers — have one main thread of execution, and, to put it simply, process or function B cannot be executed until process or function A is finished. A web page’s UI is unresponsive to any other JavaScript processing while it is occupied with executing something — this is known as DOM blocking.

What is Event Loop?

The concept of event loop is simple, There is an endless loop -> Javascript engine(chrome v8 engine, Mozilla SpiderMonkey) waits for tasks, executes them, sleeps until another task comes.

For example:

  • Whenever an external script runs <script src=””> loads, and the task is to execute it.
  • Whenever user clicks on their mouse, task is to dispatch and executes mouseclick event
  • Whenever we use setTimeOut, setInterval in our script task is to run callbacks.
  • and it continues …

During this process, there is a situation when Javascript engine busy running one task and another task comes for execution forms a task queue or in general, we call it an Event Queue.

All good then what’s the problem?

  1. webpage rendering never happens while javascript engine executing a task, even though it takes a long time. Changes to DOM can only be seen only after javascript engine completes its task.
  2. You might have seen “Page not responding” popup in your browser and ask you to kill the task, this happens if a certain task takes too long to complete, process user events, etc., because of complex calculations or infinite loops running in the single thread available for javascript.

To understand it better run the following codes

let’s take a function that counts from 1 to 1000000000.

If you run the code below, the engine will “hang” for some time. For server-side JS that’s clearly noticeable, and if you are running it in-browser, then try to click other buttons on the page — you’ll see that no other events get handled until the counting finishes.

blocking DOM during complex operations js

Live Demo: here

Now by splitting above code into multiple chunks we can avoid blocking user interface and let event loop accepts user inputs.

Note that we are using setTimeout with 0 delay in code below.

non blocking DOM during complex operations js

Live Demo: here

To understand the concept above on how using setTimeout renders DOM without any delay or not blocking user events:

All javascript engines treat setTimeout callback function as Task/Macrotask so that it can be scheduled for the next execution i.e, after looking up for any pending events or rendering jobs.

Tasks are scheduled so the browser can get from its internals into JavaScript/DOM land and ensures these actions happen sequentially. Between tasks, the browser may render updates. Getting from a mouse click to an event callback requires scheduling a task, as does parsing HTML, and in the above example, setTimeout.

In the above code on first chunk ( from 1 to 1000000) execution, rendering of DOM is paused. Once setTimeout is called to run next task of chunk it first looks for pending events like user mouseclick, mousemove, scroll or dom rendering and finishes these jobs. and comes back to scheduled setTimeout callback function by not disturbing the user from interacting. In this way, we can avoid any blocking mechanisms in the user interface.

Note: Although setTimeout is set to 0 by default, it scheduled for next cycle why? because on every event loop cycle of one macrotask, all pending microtasks will run, then macrotask next cycle, then rendering & microtasks so on…

Time to get some Microtasks:

We have seen macrotasks till now, now there is concept called microtask. Microtasks are usually scheduled for things that should happen straight after the currently executing script, such as reacting to a batch of actions or to make something async without taking the penalty of a whole new task. The microtask queue is processed after callbacks as long as no other JavaScript is mid-execution and at the end of each task. Any additional microtasks queued during microtasks are added to the end of the queue and also processed. Microtasks include mutation observer callbacks, and as in the above example, promise callbacks.

Like setTimeout, setInterval goes into macrotask category, promises goes into microtask category which means after current running task completed by javascript engine, it will immediately run microtasks before executing any macrotask again.

Let’s try something with promises (es6)

Explaining order of execution on promises
Order of execution in promises

Why does this happen?

Once a promise settles, or if it has already settled, it queues a microtask for its reactionary callbacks. This ensures promise callbacks are async even if the promise has already settled. So calling .then(yey, nay) against a settled promise immediately queues a microtask. This is why promise1 and promise2 are logged after script end, as the currently running script must finish before microtasks are handled. promise1 and promise2 are logged before setTimeout, as microtasks always happen before the next task.

What is the priority of event execution?

Event loop execution process

For better understanding of eventloop processing model follow this

https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model.

Here comes webworkers:

As per mozilla web API, Web Workers are a simple means for web content to run scripts in background threads. The worker thread can perform tasks without interfering with the user interface.

Will use the same example as above to demonstrate webworker by separating expensive operation into worker.js file.

Now we have two files

  1. webworker.html -> which create worker to run an expensive operation on worker thread instead of running on the main thread.

2.worker.js -> which holds the expensive CPU operation task and communicates with worker.html file after task completion

After running above code you will notice webworker works efficiently without blocking user interface or from rendering.

To quote Mozilla’s developer reference

Notice that onmessage and postMessage() need to be hung off the Worker object when used in the main script thread, but not when used in the worker. This is because, inside the worker, the worker is effectively the global scope. so we just terminate worker after task completion.

Note:

  • The script must be served from the same host or domain for security reasons, and that is also the reason that web workers won’t work if we open the file locally with a file:// scheme. I am using npm http-server to run files on light http server on my local.
  • Web Workers can exchange messages with the main process, but they have their own variables, and their own event loop.
  • Web Workers do not have access to DOM, so they are useful, mainly, for calculations, to use multiplle CPU cores simultaneously.

Conclusion

We started with understanding event loops and its architecture. We tried running the DOM blocking script and then we converted that script to a non-blocking DOM script by dividing execution into chunks with setTimeout(Macrotask).

Then we came through promises(microtasks) and its execution prior to macrotasks and post to DOM rendering.

Finally we gave a try on webworkers with same DOM blocking script and observed concept of parallel threading in javascript using worker threads.

As a whole In this article we have seen EventLoops, Macrotasks, Microtasks, web workers to keep up the web performance at priority while building applications. Above understandings can improve the webperformance and user experience, although we have used simple examples to demonstrate the non-blocking IO we can extend these concepts to our real world applications in much better way. Give me cheers! if you leart something new today.

Happy Coding! 💻 …

--

--

Chandu Ivaturi

Entrepreneur & Software developer by profession. Other interests include Business, Travel, Books, Economics, Technology, Space, Science, and Research.