WinRT JavaScript - Web Workers & Promises
Written by Ian Elliot   
Article Index
WinRT JavaScript - Web Workers & Promises
Communication
Promises to the rescue

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.

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.

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.

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(
               "ms-appx:///js/worker.js");
     worker.addEventListener("message",
       function (e) {     
          Button1.textContent = e.data.total;
          Button1.disabled = false;
       });
 });

 

All we do is create a Worker object, notice the use of ms-appx to indicate that the file is stored within the project, 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.

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(
               "ms-appx:///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.