How to use Web Workers to speed-up web applications?

Nowadays, JavaScript is heavily used in web applications and slow or laggy applications are not liked by anyone. This user experience can be bad for business. In this post, I’ll explain you how to use Web Workers to speed-up web applications. Your JavaScript code is single-threaded in the same context, but other operations done by the browser (e.g. AJAX request, rendering, event triggers, etc.) is not. Keep reading to understand how Web Workers can help us in this context.

All the working code samples in this post is available via my Github repository – Web Workers Play Area. I’ve used TypeScript for this post that is compiled to JavaScript. This post is linked with sample named: Web Worker Simple Demo in the repository.

Web Workers

Web Workers is an easy way to run scripts in background threads. This background thread can easily perform tasks that will not interfere with the user interface. The communication between your JavaScript code and worker is a very simple concept. Once a worker is created, it can send messages to the JavaScript code that created it by posting messages to an event handler specified by that code and vice versa.

Browser Support & Feature Detection

There are basically two different types of Web Workers: Dedicated and Shared Workers. In this post, I’ll explain about dedicated workers as this is supported by most of the modern browsers as compared to shared workers.

Dedicated Workers

As the name suggests, a dedicated worker is only accessible by the script that called it. If your application needs to do some processing or computation that may affect user interface or performance of the browser, consider moving that responsibility to a worker.

Let us create a worker that will be responsible to multiply given set of number and return the result back to the caller. This is an easy example but it demonstrates the usage of worker as shown below:

Sending Messages to and from a Dedicated Worker

The application main file – index.js file has following code.

Spawning a dedicated worker

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

let worker=newWorker('worker.js')

// let message = {

// string: 'this is a demo',

// number: 800,

// bool: true,

// array: [1, 2, 3, 4],

// literal: {

// key: 'k1',

// value: 'v1'

// }

// }

worker.postMessage([2,5,87,45,96,1245,652]);

worker.addEventListener('message',(event:MessageEvent)=>{

console.log('Message received by main is...');

console.log(event.data);

});

Let’s understand the code now:

Line no. 1: A new worker can be created by calling the Worker() constructor and also pass the URI of a script to execute in the worker thread as shown below.

Line no. 3 – 12: This is a commented code just to explain that any type of data can be sent to the worker like string, number, boolean, array, object literal, JSON. Only restriction is any function cannot be sent as it will result in run-time error.

Line no. 14: An array of numbers is sent to the worker. Imagine this data input may come from user directly or a service and is then given to the worker.

Line no. 16-18: This is how the consumer of the worker can listen to the messages by adding a ‘message’ event listener and by accessing the data property of event object. Even onmessage() can also be used here instead of addEventListener. Once the data is received from the worker, then it can be used as per the application requirements. The current code just logs the data in the console.

Script running in Worker Thread

Let’s look at the script that is executed in the worker thread as shown above.

Dedicated Worker Script

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

this.addEventListener('message',(event:MessageEvent)=>{

console.log('Message received by worker is...');

if(event.data&&event.data.length>0){

console.log(event.data);

let result=event.data[0];

for(leti=1;i<event.data.length;i++){

result=result*event.data[i];

}

this.postMessage(result);

}

else{

console.log('Array of number is expected by worker!');

}

});

Line no. 1: This is how a worker can listen to the messages that is similar to the line no. 16 in the consumer script above. The message will be in the data property of event object.

Next, if the expect data is valid, all this worker needs to do is process it as per the requirements and then return the result back to the consumer using the postMessage API. This is common to both the sides.

If you run this sample application with the help of http-server npm package (this will basically serve the files locally in your browser) and then check the console messages, the messages will be available that were logged by consumer and the worker scripts.

Terminating a Worker

It is easy to terminate a worker from the main thread as shown below. When terminate() method is called, the worker thread is killed immediately without an opportunity to complete its operations or clean up after itself.

Also, workers may close themselves by calling their own close() method as shown below.

Terminate and Close methods

JavaScript

1

2

3

4

5

// from main thread

worker.terminate();

// from worker

close();

Spawning Sub-Workers

Web Workers may spawn more workers as per the requirement. These sub-workers must be hosted within the same origin as the parent page. Also, the URIs of the sub-workers are resolved relative to the parent worker’s location rather than that of the owning page. This makes it easier for workers to keep track of where their dependencies are.

Importing Scripts and Libraries

Worker threads can use a global function importScripts(), which allows to import scripts. It accepts zero or more URIs as parameters to resources to import. Notre that the scripts may be downloaded in any order, but will be executed in the order in which you pass the filenames into this global function. This is done synchronously. The function importScripts() does not return until all the scripts have been loaded an executed.

All of the following examples are valid:

Importing scripts and libraries

JavaScript

1

2

3

4

importScripts();/* imports nothing */

importScripts('foo.js');/* imports just "foo.js" */

importScripts('foo.js','bar.js');/* imports two scripts */

importScripts('//example.com/hello.js');/* You can import scripts from other origins */

Limitations of Web Workers

Workers operate independently of the main browser UI thread so they are not able to access DOM i.e. they can’t read or modify the HTML document.

They can’t access any global variables or JavaScript functions within the main page.

Related

Siddharth Pandey is a Software Engineer with thorough hands-on commercial experience & exposure to building enterprise applications using Agile methodologies. Siddharth specializes in building, managing on-premise, cloud based real-time standard, single page web applications (SPAs). He has successfully delivered applications in health-care, finance, insurance, e-commerce sectors for major brands in the UK. Other than programming, he also has experience of managing teams, trainer, actively contributing to the IT community by sharing his knowledge using Stack Overflow, personal website & video tutorials.