Web Workers And The Threads That Bind Us
June 30th, 2016 | By Jscrambler | 8 min read
Web Workers are scripts initiated from the JavaScript code in your app. We tend to take for granted our day-to-day web browsing experience.
Our daily life and work might have us using any single-page web app that seems like a web page with some neat UI icing.
In reality, these apps hide underlying complexity that makes them quick and responsive. App responsiveness and performance might be a non-issue for the latest Macbook Pro, but our concern lies in running it on a decently powered phone.
Developers understand the value of a responsive app and strive to avoid skipped frames that result in poor performance and user experience.
Web Workers
Web Workers are especially suited for doing expensive computations and avoiding blocking the UI rendering thread entirely.
They provide us with some concurrency within Javascript by running otherwise costly tasks in the background. Javascript is, for the most part, a single-threaded environment meaning that only one method can be executed at any given time.
Let’s say, for example, the task of rendering a UI in a web app is considered a process utilizing exactly one CPU thread. When you render a button or event, the thread starts the task of painting that button on the screen and won’t run other tasks during this event. If rendering the page takes too long, the content remains unresponsive to user interaction.
The Chrome V8 JavaScript engine is undoubtedly powerful but even it can’t handle running out of memory:
Basic Example
Here’s a simple example of how you’d utilize a web worker. First, let’s initiate a web worker with Worker() as in the following main.html:
<h1>Web Workers example</h1>
<div class="controls" tabindex="0"></div>
<form>
<div>
<label for="number1">Multiply number 1: </label>
<input id="number1" type="text" value="0" />
</div>
<div>
<label for="number2">Multiply number 2: </label>
<input id="number2" type="text" value="0" />
</div>
</form>
<p class="result">Result: 0</p>
<script type="text/javascript">
// <![CDATA[
var first = document.querySelector('#number1');
var second = document.querySelector('#number2');
var result = document.querySelector('.result');
if (window.Worker) { // Check if Browser supports the Worker api.
var myWorker = new Worker("worker.js");
//creates a new web worker with the provided file
first.onchange = function() {
myWorker.postMessage([first.value,second.value]);
console.log('Message posted to worker');
}
//our event listener receives messages from the worker second.
onchange = function() {
myWorker.postMessage([first.value,second.value]);
console.log('Message posted to worker');
}
myWorker.onmessage = function(e) {
result.textContent = e.data;
console.log('Message received from worker');
}
};
// ]]>
</script>
Our script logic runs immediately as soon as it’s passed to the worker object. Our onchange event looks for any events that fire. The worker thread receives the message, logs it, and posts the appropriate string using myworker.postMessage() method and notifies the UI thread with postMessage().
As you can see web workers are easy to get started but their true potential is in running asynchronously.
As soon as our Main script is called, it begins to run in the background. Our script logic runs immediately as soon as it’s passed to the worker object.
Our event listener looks for any events that fire. The worker thread receives the message, logs it, and posts the appropriate string using worker.postMessage() method and notifies the UI thread with self.postMessage().
As you can see web workers are easy to get started with but their true potential is running asynchronously.
Blocking vs. Non-Blocking
Because it costs much more to transmit a byte than it does to compute it, we can harness the power of web workers and offload any performance load to client CPUs. Rather than burdening servers and waiting to send clients data to be rendered, workers let us do some of the computing locally in a non-thread-blocking manner.
Web Workers can asynchronously reference an external script, download that script, and run it as a background process to not interfere with the main UI thread.
Workers use post messages to communicate through messages posted to and from each worker. The Web Workers API grants us the ability to run scripts in the background with each Web Worker assigned to its thread. As workers run in the background, application logic will not block the render thread.
One web worker uses one thread, enabling you to run application logic across various windows with improved performance. We’ve covered dedicated workers but there’s also a shared worker variation that allows multiple workers access to the same file.
Here are some other important things to know when using Web Workers:
…they have no control over the DOM( or window object) so they’re unable to update the UI.
…they share no memory with your main process.
…they have access to navigator metadata like user-agent and other information.
…they can use Timers (setTimeout, setInterval)
…they also get access to XMLHttpRequest and WebSockets
Without Workers
You might still be wondering about practical applications for web workers.
Next, we’ll cover an example with and without workers to visualize the benefits. In the below example without web workers, we are generating a series of Fibonacci numbers up to a number of our choice.
Once our program runs, it fills up our results array and generates the series as an unordered list. The major key here is that when the thread begins computing a large Fibonacci number, the loading gif will lock up because UI is blocked by the thread calculating the series.
Our example without workers, worker.html:
<link rel="stylesheet" type="text/css" />
<style type="text/css">
<!-- ol {
background-color: #ccc;
width: 20%;
}
ol li {
background-color: #fff;
padding-left: 5px;
margin: 5px;
}
-->
</style>
<div id="container">
<h1>Fibonacci Web Workers</h1>
<input id="seriesLength" type="numeric" value="40" />
<input id="generateButton" type="button" value="Generate" />
<img alt="" src="http://i.imgur.com/vp8NUmC.gif" />
<ol id="log"></ol>
</div>
<script type="text/javascript">
// <![CDATA[
var results = []; //create the results array
var log;
//generates a log list with my Fibonacci series
$(function() {
log = $("#log");
$("#generateButton").click(function() {
log.html("");
var seriesLength = parseInt($("#seriesLength").val());
generateFib(seriesLength);
//recursviely generate my series of Fibonacci numbers
$.each(results, function() {
logMsg(this);
//iterate my series and log them as an unordered list
});
});
});
function calculateNextFibVal(n) {
var s = 0;
var returnValue;
if (n == 0) {
return (s);
}
if (n == 1) {
s += 1;
return (s);
} else {
return (calculateNextFibVal(n - 1) + calculateNextFibVal(n - 2));
}
}
function generateFib(n) {
results.length = 0;
for (var i = 0; i < n - 1; i++) {
results.push(calculateNextFibVal(i));
}
}
function logMsg(msg) {
log.append("
< li > " + msg + " < /li > ")
}
// ]]>
</script>
With Workers
The structure for our example with workers is generally the same. The only difference is that we move the logic to it’s worker script.
Worker.html:
<link rel="stylesheet" type="text/css" />
<style type="text/css">
<!-- ol {
background-color: #ccc;
width: 20%;
}
ol li {
background-color: #fff;
padding-left: 5px;
margin: 5px;
}
-->
</style>
<div id="container">
<h1>Fibonacci Web Workers</h1>
<input id="seriesLength" type="numeric" value="40" />
<input id="generateButton" type="button" value="Generate" />
<img id="loadImg" alt="" src="http://i.imgur.com/vp8NUmC.gif" />
<ol id="log"></ol>
</div>
<script type="text/javascript">
// <![CDATA[
var log;
var loadImg;
var worker;
//generates a log list with my Fibonacci series
$(function() {
log = $("#log");
loadImg = $("#loadImg");
loadImg.hide();
$("#generateButton").click(function() {
var seriesLength = parseInt($("#seriesLength").val());
log.html("");
loadImg.show();
worker = new Worker("worker.js");
worker.onmessage = messageHandler;
worker.postMessage(seriesLength);
});
function messageHandler(e) {
var results = e.data;
$.each(results, function() {
logMsg(this);
});
}
function logMsg(msg) {
log.append("
< li > " + msg + " < /li>
")}
});
// ]]>
</script>
We are creating a new instance of worker with the worker when we initialize the worker() variable. We then process the onMessage() message handler and post the message to the worker.
The worker gets its commands by posting messages to the worker and then back up to the window. We get the values of my array from the worker while it loops through the results and creates an ordered list. The difference is that we’re not accessing from a local array the values that are passed in from the worker.
Worker.js:
var results = [];
function messageHandler(e) {
if (e.data > 0) {
generateFib(e.data);
}
}
function calculateNextFibVal(n) {
var s = 0;
var returnValue;
if (n == 0) {
return (s);
}
if (n == 1) {
s += 1;
return (s);
} else {
return (calculateNextFibVal(n - 1) + calculateNextFibVal(n - 2));
}
}
function generateFib(n) {
results.length = 0;
for (var i = 0; i < n - 1; i++) {
results.push(calculateNextFibVal(i));
}
postMessage(results)
}
addEventListener("message", messageHandler, true);
The worker is in a separate javascript file that is assigned to the worker thread. When we call
postMessage() our message event will fire and show up in the event arguments for our results data.
Compared to our version that doesn’t use workers, this worker-assisted version will calculate a larger series without blocking the UI thread all while allowing me to interact with the UI.
Try generating a series of 40 or above to see a notable difference in performance. You can continue to highlight items, press the button, or even generate more numbers while it calculates a new series and we won’t see any unresponsive script errors.
It can be easy to take responsive web app experiences for granted.
The modern web makes it so that we can enjoy memory-intensive apps and games requiring heavy-duty computational resources, typically from mobile devices.
Tasks like parsing large JSON data sets, visualizing analytics, or sound and image processing can slow down an already overloaded client, negatively affecting user experience. Unless of course, we can leverage our resources effectively with tools like Web Workers.
Pay special attention if you are developing commercial JavaScript apps with sensitive logic. You can protect them against code theft, tampering, and reverse engineering by starting your free Jscrambler trial.
Jscrambler
The leader in client-side Web security. With Jscrambler, JavaScript applications become self-defensive and capable of detecting and blocking client-side attacks like Magecart.
View All ArticlesMust read next
Handling CPU-Intensive Work Using Web Workers in Angular
Web workers are great for improving the performance of web applications. They can be used in Angular to handle CPU-intensive tasks.
August 6, 2020 | By Jay Raj | 6 min read
How to manipulate DOM using a service worker
Service workers are JavaScript workers that run in the background of a page, act as a proxy between the browser and the server, and manipulate the DOM.
February 14, 2023 | By Jscrambler | 4 min read