How to use Shared Web Workers in web applications?

I recently published a post that explains how to use Web Workers to speed-up your web applications and also about Dedicated Web Workers. In this post, I’ll explain how to use Shared Web Workers in web applications. Refer to my post linked above if you are interested to understand the basics of web workers.

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: Shared Web Worker Demo in the repository.

Shared Web Workers

A shared web worker is accessible by multiple scripts — even if they are being accessed by different windows, iframes or even workers but each context must be from the same origin (same protocol, host and port). This background thread can easily perform tasks that will not interfere with the user interface.

Browser Support & Feature Detection

To use HTML5 Web Workers API, developers also need to take care of browser support for this API. This is very easy to check at run-time by using following native or Modernizr code. Compared to dedicated web worker, shared web workers is less supported in the modern browsers. Please check the linked site for more information.

Shared worker feature detection

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

// option 1: native way to check using window object

if(window.SharedWorker){

// supported

}

else{

// not supported

}

// option 2: Use Modernizr for detection

// JS

if(Modernizr.sharedworkers){

// supported

}else{

// not-supported

}

// CSS

.no-sharedworkers.box{color:red;}

.sharedworkers.box{color:green;}

Example

Let us use the same example from my previous post about dedicated workers which is to create a shared web worker that will be responsible to multiply given set of number and return the result back to the caller. Along with the result, we will extend the logic to also send back decremented result (result–) from worker to the caller in every 500 ms. If a new instance of application is connected to the worker, the counter used for decremented result will be reset to the result of multiplying the given numbers.

This is an easy example but it demonstrates the usage of shared web workers such that it supports multiple contexts and the logic can be shared across all of the consumers. In our example, it is to pass some numbers and multiply the given set of number and get the result back.

Configure Chrome to show Shared Web Workers console messages

Because shared web workers can work with multiple contexts, it doesn’t use the application’s console context. It has its own console context where we will need to check the messages. For chrome, navigate to chrome://inspect and then click on Shared Workers when your application is running with the code explained below. There will be two options available for each shared web worker – inspect and terminate.

Inspect can be used to view console messages or errors and terminate option will just terminate the worker thread.

Sending messages to and from a Shared Web Worker

Sending messages to and from a Shared Worker

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

let sharedWorker=newSharedWorker('worker.js');

// let message = {

// string: 'this is a demo',

// number: 800,

// bool: true,

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

// literal: {

// key: 'k1',

// value: 'v1'

// }

// }

sharedWorker.port.start();

sharedWorker.port.postMessage([2,5,7,5,6,10]);

sharedWorker.port.addEventListener('message',(event)=>{

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

console.log(event.data);

});

Let’s understand the code now:

Line no. 1: A new shared worker can be created by calling the SharedWorker() 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: With a shared worker, you have to communicate via a port object – an explicit port is opened that the scripts can use to communicate with the worker (this is done implicitly in the case of dedicated workers). We need to manually call start() method to set up the communication.

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

Line no. 16-19: This is how the consumer of the worker can listen to the messages by adding a message event listener via port 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.

Sahred Worker Script

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

let counter=0

this.addEventListener('connect',(event)=>{

console.log(event);

let port=event.ports[0];

port.start();

port.addEventListener('message',function(e){

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

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

console.log(e.data);

let result=e.data[0];

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

result=result*e.data[i];

}

port.postMessage(result);

counter=result;

setInterval(function(){

counter--;

port.postMessage('Result-- is '+counter)

},500);

}

else{

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

}

});

});

Line no. 3: A shared worker script needs to listen to connect event so that whenever any external context requests a connection, this script can respond appropriately.

Line no. 6: The event object will give access to ports array that can help us to get the exact port who made the request. Note that instead of listening to message event, we can also maintain an array to save port and use this array to post messages to multiple contexts that are connected to the shared worker.

Line no. 7 -31: Next, it is important to manually call the start() method on port so as to allow communication. Now, this port also needs to listen to messages by registering an message event listener as shown in the code 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.

Along with the result, we will extend the logic to also send back decremented result (result–) from worker to the caller in every 500 ms. If a new instance of application is connected to the worker, the counter used for decremented result will be reset to the result of multiplying the given numbers.

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.

Close a connection

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

Close a connection

JavaScript

1

2

// from caller/consumer

sharedWorker.port.close();

With multiple connections in case of shared web worker, the port connection will be closed only for the instance for which close() method is called. All other connections will still be open. You can see this working if you run the application in different tabs and then try to close random port, other(s) will remain as active.

To close the port from a shared worker itself, use port.close() method. Note that the connection will be closed but if you inspect in Chrome using chrome://inspect/#workers, a worker will be displayed as it has not been terminated, only the connection was closed. This means the consumers/callers cannot communicate with this shared worker anymore as there is no connection anymore.

Refer to my previous post to learn about how to import scripts, libraries and limitations of web workers.

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.