This is nice because you don’t have to worry about the concurrency issues and is still well performing for simple pages and common tasks, however, if you need to do complex and CPU-heavy stuff like images transformations or hard math client side, having multiple threads would be great in order to not block the UI (User Interface) while computing.
The UI updates and interactions are handled in the same main thread, thus if you have a long running task, for example an image transformation which needs 10s to be computed, you are blocking the UI responses (ex. JS animations, clicks, inputs, typing, etc.) for 10s, and the browser itself could decide to take action and show a popup to the user asking whether to kill that process or keep it running.
Creating non-blocking UIs
There are however some workarounds to this problem, let’s see what you can do for running a long script as well as not blocking your UI:
- Slice your long-task in little sub-tasks and run them asynchronously, this way between a sub-task and the next one the main thread has the opportunity to do other things (like updating the UI). You can use the setTimeout API for this and take advantage of the Event queue logic to have other things (Job queue and Rendering) done before the next Event Loop tick. (you can find your
Array.prototype… ok, just joking )
Here I created this graphic, because visualized is better! This is the big picture of what’s happening under the hood:
When your code is executed it may call the Browser’s APIs to interact with the DOM or schedule some async task. Those async tasks are added to the Event queue or to the prioritizedJob queue (if using Promises). As soon as the the Call Stack has finished to process the current tick (is empty), the Event Loop feeds it with a new Tick (which is composed by ONE callback, the FULL job queue, and the POSSIBILITY to call, fully or only some parts, the Render queue).
Let’s see everything one by one
- Call Stack: it is the place where your code is executed (your functions are loaded and executed, V8 engine in Chrome and NodeJS), it is basically a LIFO stack (last-in-first-out), when it is empty, a.k.a. has completed all the current Tick tasks, it becomes ready to accept the next Tick from the Event Loop;
- Browser APIs: a link between your code and the browser’s internals to schedule tasks, interact with the DOM and more (ex. setTimeout, AJAX, createElement, querySelector, append, click, etc.). In case of callbacks they will add your callback code to the Event queue, instead, in case of a then (promise’s method), your then-code will be added to the Job queue;
- Event queue: every time you add a callback (ex. via the setTimeout or the AJAX APIs), it is added to this queue;
- Job queue: this queue is reserved for promise’s thens, it is a prioritized queue, its meaning is like ‘execute this code later (= asynchronously), but as soon as possible! (= before the next Event Loop tick)’, and this is why browsers had introduced this new queue to fulfil the Promises specifications;
- Render Queue:
this will be explained soon in another article, stay tunedhere the article ;
- Next Tick: it is what will be executed next, basically it’s composed by ONE callback from the Event queue, THE FULL Job queue (this point is important, the current tick will finish only after the Job queue is empty, so you may inadvertently block it from going to the next Tick if you continuously add new jobs to this queue), may re-render (execute the necessary steps in the Render queue to update the screen);
- Event Loop: it monitors the Call Stack, as soon as it is empty (has finished to process the current tick), the Event Loop feeds it with the next Tick.
Along the main thread there are many other threads spawned by the browser to do useful stuff:
- Parser Thread: parses your code in machine-understandable trees;
- Statistics collector Thread: collects data and statistics to discover insights about your code (the scope is to optimize it runtime);
- Optimizer Thread: uses the statistics and insights collected by the Statistics collector Thread to make performance optimizations over your code (Caching, Inlining, etc.);
- Rasterizer Thread: rasterize your graphic into frames.
Smarter garbage collection for smoother browsing and less memory usage
Incremental GC in Firefox 16!
For updates, insights or suggestions, feel free to post a comment below!