JavaScript Web Workers
Written by Ian Elliot   
Wednesday, 05 September 2012
Article Index
JavaScript Web Workers
Communication

Basic Communication Methods

So if the Web Worker is isolated from the UI thread how do the two communicate?

The answer is that they both use events and a method that causes events on the other thread.

UI Thread To Worker Thread

Let's start with the UI thread sending a message to the worker thread. The Worker object that is created on the UI thread has a postMessage method which trigger a message event on the worker thread. Notice that this is where the thread crossover occurs. The Worker object is running on the UI thread but the even occurs in the code running on the worker thread.

For example:

var worker=Worker("myScript.js");
worker.postMessage({mydata:"some data"});

The postMessage method triggers a message event in the worker code and sends it an event object that includes the data packaged as an object or an array.

To get the message sent to the Worker object you have to set up and event handler and retrieve the event object's data property.

For example, in the Web Worker code you would write:

this.addEventListener("message", function (event) {

In the Web Worker code the global context is provided by this or self and this gives access to all of the methods and objects documented.  To get the message you would use:

var somedata = event.data.mydata;

Of course as you are passing an object to the event handler you could package as many data items as you needed to.

Notice that it is important to be very clear what is going on here. The postMessage method call is on the UI thread but the event handler is on the worker thread.

Worker Thread To UI Thread

Passing data from the worker thread to the UI thread works in exactly the same way - only the other way round. You use the postMessage method in the worker thread and attach an event handler for the message event in the UI thread.

For example, in the worker code:

this.postMessage({mydata:"some data"});

This triggers a message event in the UI thread and you can define a handler and retrieve the data using

worker.addEventListener("message", 
 function (e) {
    var somedata= e.data.mydata;
 });

Once again you have to be very clear that you understand what is running where. In this case the postMessage method is running on the worker thread and the event handler is running on the UI thread.

This is about all there is to using Web Workers.

There are some details about error handling and terminating the thread before it is complete but these are details. The general idea is that you use the message event to communicate between the two threads.

There is one subtle point that is worth keeping in mind. The events that you trigger in passing data between the two threads will happen in the order that you trigger them but they may not be handled promptly.

For example, if you start your worker thread doing an intensive calculation then triggering a "how are you doing" message event from the UI thread might not work as you expect. It could be that the worker thread is so occupied with its task that events are ignored until it reaches the end and this is not what you might expect. The same happens with the messages passed from the worker thread but in this case the UI thread is generally not so focused on one task and so events usually get processed.

in general events going from the worker thread to the UI get processed as part of keeping the UI responsive. Events going the other way, i.e. from the UI thread to the worker, are not so reliable.

Slow Calculation As a Worker

As an example of using Web Workers we can convert the slow function given earlier into a worker. We need a new JavaScript file and to make things simple we will only pass data back from the calculation to the UI.

Add a JavaScript file called Worker.js to the js directory and enter the following code:

var total = 0;
for (var i = 0; i <= 10000000; i++) {
    total += i;
};
this.postMessage({ total: total});

Notice that we don't need to wrap the code as a function but you can if you want to. Also notice that there is no return to supply the result. In this case the result of the calculation and the fact that the thread has ended are signaled by triggering a message event.

The code in the UI thread is just as simple:

Button1.addEventListener("click", 
 function (e) {
     Button1.disabled = true;
     var worker = Worker("worker.js");
     worker.addEventListener("message",
       function (e) {      
          Button1.textContent = e.data.total;
          Button1.disabled = false;
       });
 });

 

All we do is create a Worker object based on worker.js. This starts running immediately on the worker thread. The message event handler simply sets the button's text to the result. Also notice that it is a good idea to disable the button while the worker thread is running to stop an impatient user clicking it again and accidentally launching another worker thread. The need to protect against such multiple clicks is one thing that is different about programming with worker threads.

If you run this program you will discover that Button1 is disabled while the worker thread is running but the other button and any other controls you have included all work perfectly normally and the finally result of the calculation just appears a while later.

Passing Data To Slow Calc

Having the worker thread start immediately is simple and its a good example but it isn't realistic. In most cases the stages of using a Worker object are to first create it and then to send it some data to initialize it and get it started.

As an example of this slightly more complicated pattern and of passing data from the UI thread to the worker thread let's set the number of iterations the for loop performs.

In the main JavaScript i.e. the UI thread the code is:

Button1.addEventListener("click", 
 function (e) {
    Button1.disabled = true;
    var worker = Worker("js/worker.js");
    worker.postMessage({ number: 1000 });
    worker.addEventListener("message", 
      function (e) {
         Button1.textContent = e.data.total;
         Button1.disabled = false;
      });
  });

 

Notice that the only difference is that now we has a worker.postMessage call that sends a value of 1000 to the worker.

The code for the Worker thread now becomes:

var number=0;
var total = 0;

this.addEventListener("message",
    function (event) {
      number = event.data.number;
      for (var i = 0; i <= number; i++) {
         total += i;
      };
      this.postMessage({ total: total});   
   });

 

Notice that now the message event handler starts the computation off and returns the result by triggering a message event in the other direction. If you run the program you should notice no change apart from being able to set the number of iterations from the UI thread.

Conclusion

The basic pattern is - create a worker thread, use the message event to pass data from the worker thread to the UI thread and vice versa.

If the worker thread needs to display some results to then you pass the data to the UI thread and get it to work with the DOM to set textboxes etc. If the worker thread needs to get user input the UI thread reads the data, packages it and sends it to the worker thread via the interrupt. In each case the parameters of the event handers are pass by value and so a copy is made of the data for the receiving thread to work on. In this way the two threads can share data without sharing objects. 

There are some more sophisticated features that you can use. For example you can allow a web worker to create additional threads. You can also use "transferable objects" which avoid the overhead of making a copy of the data when an event occurs. This may be more efficient but it is most supported by all browsers (only chrome at the moment) and so is best avoided. For most purposes the basic Web Worker should do what you need and for what it provides it is a very simple mechanism.

 

To be informed about new articles on I Programmer, install the I Programmer Toolbar, subscribe to the RSS feed, follow us on, Twitter, Facebook, Google+ or Linkedin,  or sign up for our weekly newsletter.

 

Banner


JavaScript Canvas - Fetch API

Working with lower-level data is very much part of graphics. This extract from Ian Elliot's book on JavaScript Graphics looks at how to use typed arrays to access graphic data.



JavaScript Jems - The Inheritance Tax

JavaScript should not be judged as if it was a poor version of the other popular languages - it isn't a Java or a C++ clone. It does things its own way.  In particular, it doesn't do inheritance  [ ... ]


Other Articles

 

kotlin book

 

Comments




or email your comment to: comments@i-programmer.info



Last Updated ( Wednesday, 05 September 2012 )