Parallelism in JavaScript: build super programs🔥🔥
Concurrency vs Parallelism
concurrency:
single object performing multiple tasks( example: a juggler)
we already talked about this system in the previous chapter: the task queue and microtask queue which are both executed by a single thread (interchanged), the main thread.
both async and sync code in JS is executed by a single thread, which juggles both of them based on the state of the event loop.
Concurrency example
// doing multiple tasks in a period of time
task a task b task c
concurrency:
task a
task c
task a
task b
task a
task c - complete
task b
task a
task b
task a - complete
task b - complete
final result
a single thread juggles multiple tasks, giving the illusion that they are happening at the same time.
parallelism
multiple objects working at the same time, on one or multiple tasks
task a task b task c
task a task b task c
task a task b complete
task a complete complete
task a complete complete
complete complete complete
final result
Multiple independent objects, working independently of each other(not interleaved) this is usually achieved through multiple threads and cores, languages such as java have this feature built in I believe.
Parallelism in browsers
Browsers are fundamentally single threaded, having only the main thread handling both the execution of JavaScript code and rendering the browser window, async programming does relieve the main thread by pausing execution of specific code, but in the end even that code will run on the main thread, needless to say the main thread works pretty hard, which is actually the source of "a script is slowing down your browser" message, when a script is taking to long to finish a task and blocks the main thread, while async is the solution, an even better solution is creating a new thread and that is where web workers come in.
web workers
a web worker creates/spawns a second JS thread separate from the front end browser, the thread does not have access to the DOM, window and anything in the front-end browser accept given by the main thread, all the is, is JS, this is true parallelism: the idea of two separate threads not inability to access the DOM, these threads run at the same time without blocking each other.
they communicate via a message system, they are able to send messages to each, which can be strings, objects or simple values.
This way we can migrate heavy computation from the main thread to the 2nd, and allow the main to perform it's primary duty to handle use input and react seamlessly.
This is a true game changer, you can literally perform heavy tasks in the worker, without the browser missing a frame, this is ultimate optimization.
getting started with workers
because workers run in the browser we need an HTML file for this part,
create three files:
index.html
main.js
worker.js
I will be using vscode live server plugin to serve index.html, you can use whatever you like, or even a bundler like parcel which support imports and live reload.
Goal: create a second thread running an infinite loop, while the browser's main thread plays animation at 60FPS.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<label id="label"></label>
<script src="main.js"></script>
</body>
</html>
in main.js:
// main thread
/**
* @type {HTMLLabelElement}
*/
const label = document.getElementById("label")
const skills = ["react", "vue", "angular", "ionic", "nativescript", "html", "css", "sass"]
// simple DOM update
setInterval(() => {
// choosing a random skill every 16ms and updating the label element to show that skill
let rand = Math.floor(Math.random() * skills.length - 1);
label.innerText = skills[rand]
}, 16);
I know this does not seem much, given that set interval is a microtask, but if we add an infinite loop in the main file, one of two things will happen your browser will trash or not update the UI at all, since the main thread is stuck in this infinite loop, because of the run-to-completion rule, you can test it by adding an infinite loop in main.js
while(true){
}
this sets us up nicely to prove that a worker spawns a new thread separate from the browser window and document, if we can run an infinite loop logging something in the worker thread while updating the browser successfully every 16ms this will prove that these threads are separate,
remove the infinite loop in main and add the following on top
// creates a worker thread(spawning a new thread)
// Worker() takes name of an existing js file, which the worker will load in it's own environment
// separate from the the main js and it's thread
// every code in worker.js will run in the second thread
const worker = new Worker("worker.js")
// we use the worker object to communicate and receive communcication from the second thread
// sending a msg to the second thread
// the msg can be an object, stringified JSON object, buffer arrays etc
// but you cannot send DOM elements, classes etc
worker.postMessage("hello there")
open worker.js
//worker.js thread
//catching/receiving messages
// self = refers to the worker,
// listening to messages
self.onmessage = e => {
// logging the recieved message
console.log(e.data)
// sending back a message to the main thread after 10 seconds
setTimeout(()=> {
// sending a message to main thread
postMessage("after 10 000 milliseconds")
}, 10000)
}
In main.js we can also listen to messages from the second/worker thread using the worker object
worker.onmessage = e => {
console.log(e.data, "from second thread")
}
if you reload, in the console you will see worker.js logging "hello there" and after 10000ms the main thread will receive a message from worker and logs it
the infinite loop experiment
in the worker
self.onmessage = e => {
...
}
let index = 0;
// infinite loop
while(true){
// logging at an interval, logging at every iteration will crash the browser
if(index % 10000000000){
console.log("while loop")
}
index += 0.00000000000000000000000000000001;
}
magic, the browser is not skipping a bit, while the infinite loop is running, if you have been using JS for a while, you'll understand how much of a big deal this is, just having a while(true) statement in JavaScript is super impressive.
the browser might crash because of the frequent console logs, make sure you clear the console while it is running.
Using this simple architecture there are many possibilities: operating on big files, large amounts of data and algorithms, only sending the computation result to the main thread.
In term of the DOM access, there are libraries out there, for one workerDom which allows manipulation of the DOM in the worker, workerDom also works well with major front-end frameworks.
With that we have achieved true parallelism in JavaScript.
This is an excerpt from an eBook JavaScript for advanced beginners available on gumroad as a pre-order, and should be launching soon,
The eBooks main goal is to provide a gentle but needed push towards advanced JS, range of topics are covered from Object Oriented JS, Object composition to generators, promises, computational media and metaprogramming