DEV Community

Cover image for HTML Web Workers in Depth
ANISHA SWAIN | The UI Girl
ANISHA SWAIN | The UI Girl

Posted on • Originally published at Medium

HTML Web Workers in Depth

JavaScript running in the background, without affecting the performance of the page.

Are you a person who just started with HTML/CSS or someone who wants to have an in-depth knowledge of the advanced features of HTML? Then you are in the right place. So grab a cup of coffee and enjoy the first part of our HTML series, HTML Web Workers in Depth.

Problem

Consider a situation where you need to handle UI events or a query that processes large amounts of API data or manipulates the DOM. What does your JavaScript do then? JavaScript will hang the browser if the CPU utilization is very high. JS runs in a single-threaded environment which means multiple scripts cannot run at the same time. So that means function B cannot be executed until function A is finished. meanwhile, during this process, the HTML page becomes unresponsive until the script is finished.

*We can mimic the ‘concurrency’ concept by using techniques like **setTimeout(), setInterval(), XMLHttpRequest **and event handlers. All of these features run asynchronously and doesn’t block anything but these doesn't necessarily mean concurrency.*

wiki: concurrency is the ability of different parts or units of a program, algorithm, or problem to be executed out-of-order or in a partial order, without affecting the final outcome*.*

Solution

And there comes web worker to the rescue! A web worker is a JS script that runs in the background in a separate thread i.e. it runs independently of other scripts, without affecting the performance of the page. We can continue to do browsing, mouse events etc. uninterrupted, while the web worker runs long scripts in the background. This results in the web page being responsive.

Note: Even though, Web Workers are relatively heavy-weight background scripts, they are not intended to be used in large numbers.

Why to use JavaScript Web Workers?

  1. Can utilize parallel programming to perform multiple operations simultaneously

  2. Can create background threads that are separate from the main execution thread

  3. Can run expensive operations within an isolated thread hence increasing responsiveness and speed.

  4. Web-workers are the kernel-level thread.

  5. Web-workers requires more space and CPU time.

  6. Web-worker executes codes on the client-side (not server-side).

  7. Web worker threads communicate with each other using postMessage() callback method to get notified upon script completion

Types of Web Workers (Dedicated and Shared Workers)

In HTML5 Web Workers are of two types:

  • Dedicated Web Workers:

The dedicated worker can be accessed by only one script which has called it. The worker thread ends as its parent thread ends.

  • Shared Web Workers:

The shared worker can be shared by multiple scripts and can communicate using a port. Shared workers can be accessed by different windows, iframes or workers.

A dedicated worker is only accessible from the script that first spawned it, whereas shared workers can be accessed from multiple scripts.

Creating a Web Worker file:

1. Creating an action to start Web Worker

we need to get a trigger point to start the worker file (For example with a button click or with any other mouse event)

    <!-- An action to start Web Worker -->
    <button onclick="startWorker()">Start Worker</button>

    <!-- An action to stop Web Worker -->
    <button onclick="stopWorker()">Stop Worker</button>
Enter fullscreen mode Exit fullscreen mode

2. Check for Web Worker availability

we can check the availability of the worker functionality by writing simple conditional statements like

if(typeof(Worker) !== “undefined”) {
    <!--    Start the Worker -->
}
Enter fullscreen mode Exit fullscreen mode

3. Creating a Web Worker Object / Spawning a worker

A worker is an object created using a constructor (e.g. Worker()) that runs a specific JavaScript file.

<!-- creation of worker object -->
Worker(aURL, options);
Enter fullscreen mode Exit fullscreen mode

Parameters:

  1. aURL is a string that represents the URL of the script that we want the worker to execute.

  2. options is an object to customize the Worker instance. The available options are type, credentials, and name. However, we don’t necessarily need to specify them

Now, if the specified javascript file exists, the browser will spawn a new worker thread, which will work asynchronously. If the path to the URL returns a 404 error, the worker will fail silently.

Listener to the web worker (onmessage( ))

The main thread listens to the worker with onmessage event. The messages can be sent and received from different threads to each other. For example:

    <!-- src/main.js -->
    const worker = new Worker("../src/worker.js");
    worker.onmessage = e => {
       document.getElementById(“result”).innerHTML = e.data;
    };
Enter fullscreen mode Exit fullscreen mode
  1. From the main thread to worker thread:
   <!-- src/main.js -->
    const worker = new Worker(“../src/worker.js”);

    <!-- recieve message from worker -->
    worker.onmessage = e => {
     const message = e.data;
     console.log(`[From Worker]: ${message}`);
    };

    <!-- post message to worker -->
    worker.postMessage(“Marco!”);
Enter fullscreen mode Exit fullscreen mode
  1. From worker thread to main thread:
    <!-- src/worker.js -->
    onmessage = e => {
      const message = e.data;
      console.log(`[From Main]: ${message}`);

    postMessage("Polo!");
    };
Enter fullscreen mode Exit fullscreen mode

So the result will be :

    <!-- [From Main]: Marco! 
         [From Worker]: Polo! -->
Enter fullscreen mode Exit fullscreen mode
  1. Sending messages between main and worker threads infinitely
    // src/main.js

    const worker = new Worker(“../src/worker.js”);

    worker.onmessage = e => {
     const message = e.data;
     console.log(`[From Worker]: ${message}`);

    const reply = setTimeout(() => worker.postMessage(“Marco!”), 3000);
    };

    worker.postMessage(“Marco!”);
Enter fullscreen mode Exit fullscreen mode

Full Example:

    <html lang="en">
      <head>
        <title>Web Workers Demo</title>
        <meta charset="UTF-8" />
      </head>

    <body>
        <div id="app"></div>
        <h1>Web Workers Demo: The Basics</h1>
        <button onclick="startWorker()">Start Worker</button>
        <button onclick="stopWorker()">Stop Worker</button>

    <script src="src/main.js"></script>
      </body>
    </html>

    //main.js

    function startWorker() {
     var w;

    if(typeof(Worker) !== “undefined”) {
        if(typeof(w) == “undefined”) {
          w = new Worker(“demo_workers.js”);
        }
        w.onmessage = function(event) {
          document.getElementById(“result”).innerHTML = event.data;
     };
     } else {
        document.getElementById(“result”).innerHTML = “Sorry, your       browser does not support Web Workers…”;
      }
    }

    function stopWorker() {
      w.terminate();
      w = undefined;
    }
Enter fullscreen mode Exit fullscreen mode

Once the Web Worker is spawned, communication between a web worker and its parent page is done using the postMessage() method.

    //workers.js (external javascript file)

    var i = 0;

    function timedCount() {
     i = i + 1;
     postMessage(i);
     setTimeout(“timedCount()”,500);
    }

    timedCount();
Enter fullscreen mode Exit fullscreen mode

Note: By using setInterval() and setTimeout() JavaScript functions also we can make web workers perform periodic tasks.

Terminate a Web Worker

When a web worker object is created, it will continue to listen for messages (even after the external script is finished) until it is terminated.

To terminate a web worker, and free browser/computer resources, use the terminate() method:

    w.terminate();
Enter fullscreen mode Exit fullscreen mode

Handling Errors

    worker.onerror = function (event) {
          console.log(event.message, event);
    };
Enter fullscreen mode Exit fullscreen mode

Reuse the Web Worker

If you set the worker variable to undefined after it has been terminated, you can reuse the code:

w = undefined;
Enter fullscreen mode Exit fullscreen mode

Full example: https://auth0.com/blog/speedy-introduction-to-web-workers/

Spawning subworkers

Workers may spawn more then one worker if they wish. These are called subworkers. These subworkers must be hosted within the same origin as the parent page. Also, the URLs for subworkers should be relative to the parent worker’s location rather than that of the owning HTML page. This makes it easier for workers to keep track of where their dependencies are.

Using multiple JS files

If the application needs to use multiple JavaScript files, we can import them using a global function, importScripts() method which takes file name(s) as argument separated by a comma as follows −

    importScripts("script1.js", "script2.js");
Enter fullscreen mode Exit fullscreen mode

The browser loads each listed script and executes it. Any global objects from each script may then be used by the worker. If the script can’t be loaded, NETWORK_ERROR is thrown, and subsequent code will fail.

Example:

This demo uses a worker script (script1.js), which imports another simple script using importScript that defines a variable, a.

    <!-- script in index.html -->
    var worker = new Worker("script1.js");

    worker.addEventListener("message", function(e){
        alert("Hello " + e.data);
    });

    worker.postMessage(true);
Enter fullscreen mode Exit fullscreen mode

1st JS File

    <!-- script1.js -->
    self.addEventListener("message", function(e){
        var a = "World";
        importScripts("script2.js");
        self.postMessage(a);
    });
Enter fullscreen mode Exit fullscreen mode

2nd JS File

    a = "Hello";
Enter fullscreen mode Exit fullscreen mode

Even though importScripts is sync, it imports the variable from 2nd script into the global scope, not the current function scope. The local variable a stays “world”, instead of being updated to “Hello”.

Note: In the context of a worker, both self and this reference the global scope for the worker.

Web Workers and the DOM

Since web workers are in external files, they do not have access to the following JavaScript objects:

  • The DOM object (as it’s not thread-safe)

    You can’t directly manipulate the DOM from inside a worker.

  • The window object

    workers run in another global context that is different from the current window. Thus, using the window shortcut to get the current global scope (instead of self) within a Worker will return an error.

  • The parent object

    In addition to the standard JavaScript set of functions (such as String, Array, Object, JSON, etc), there are a variety of functions available from the DOM to workers. This article provides a list of those.

    Not sure when to use web workers? The most common tasks include spell checking, syntax highlighting, prefetching and caching data.

Spawning a shared worker

Spawning a new shared worker is pretty much the same as with a dedicated worker, but with a different constructor name. It uses SharedWorker object.

var myWorker = new SharedWorker('worker.js');
Enter fullscreen mode Exit fullscreen mode

One big difference is that with a shared worker we have to communicate via a port object . An explicit port is opened which the scripts can use to communicate with the worker . The port connection needs to be started either implicitly by use of the onmessage event handler or explicitly with the start() method before any messages can be posted.

Calling start() is only needed if the message event is wired up via the addEventListener() method.

Sending messages to and from a shared worker

Now messages can be sent to the worker as before, but the postMessage() the method has to be invoked through the port object.

    var eventTrigger1 = document.querySelector(‘#event1’);

    var eventTrigger2 = document.querySelector(‘#event2’);

    if (!!window.SharedWorker) {
     var myWorker = new SharedWorker(“worker.js”);

    eventTrigger1.onchange = function() {
        myWorker.port.postMessage(eventTrigger1.value);
        console.log(‘Message posted to worker’);
     }

    myWorker.port.onmessage = function(e) {
     eventTrigger2.textContent = e.data;
     console.log(‘Message received from worker’);
     }
    }
Enter fullscreen mode Exit fullscreen mode

Now, on to the worker, it is a bit more complex here as well in worker.js:

We use the ports attribute of this event object to grab the port and store it in a variable.

onconnect = function(e) {
      var port = e.ports[0];

      port.onmessage = function(e) {
        var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
        port.postMessage(workerResult);
      }
    }
Enter fullscreen mode Exit fullscreen mode

Transferring data

Data passed between the main page and workers is copied, not shared. Objects are serialized as they’re handed to the worker, and subsequently, de-serialized on the other end. The page and worker do not share the same instance, so the end result is that a duplicate is created on each end. Most browsers implement this feature as structured cloning.

Note: know more at link

Transferrable objects

With Transferable Objects, data is transferred from one context to another with zero-copy, which vastly improves the performance of sending data to a Worker. For example, while transferring an ArrayBuffer from the main app to the worker, the original ArrayBuffer is cleared and no longer usable. Its contents are transferred to the worker context.

Know more at link

More Advanced Topics:

sub workers: Workers have the ability to spawn child workers. This is great for further breaking up large tasks at runtime.

Inline workers: With Blob(), you can "inline" your worker in the same HTML file as your main logic by creating a URL handle to the worker code as a string:

Thread safety: Since web workers have carefully controlled communication points with other threads, it significantly decreases the chance of concurrency problems. There’s no access to non-thread-safe components or the DOM. And you have to pass specific data in and out of a thread through serialized objects.

Restrictions with local Access: Due to Google Chrome’s security restrictions, workers will not run locally (e.g. from file://) in the latest versions of the browser. So to run the app from the file:// , we need to run Chrome with the --allow-file-access-from-files.

Same Origin Considerations: We cannot load a script from a data: URL or javascript: URL, and an https: page cannot start worker scripts that begin with http: URLs.

Use Cases

Although the concept might not sound very interesting to most of the people, it’s very useful to understand the concepts of web workers. Here are a few use cases where web workers will work like a charm

  • Code syntax highlighting or another real-time text formatting

  • Spell checker

  • Background I/O or polling of web services

  • Image filtering in <canvas>

  • Updating many rows of a local web database

Resources:

  1. https://html.spec.whatwg.org/multipage/workers.html

  2. https://auth0.com/blog/speedy-introduction-to-web-workers/

  3. https://www.html5rocks.com/en/tutorials/workers/basics/

  4. https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers

  5. http://pagedemos.com/importscript/

Top comments (0)