HTML 5 Web Workers

This article describe the unresponsive script problem in HTML5.

Web Worker

A web worker is a JavaScript running in the background, without affecting the performance of the page. They are generally external script files that run in the background. It is supported by majority of browsers:
 
Advantages 
We know web browsers increased a lot over the past few years and it is primarly because of lot of work done on its engines, ex- V8 (Google), Chakra (Microsoft). The JavaScript so far runs in a single thread. The problem with single threaded architecture is that it blocks the code and UI becomes unresponsive incase of running complex script. There are various ways to solve this problem:
  • Offload work to server, but to make apps faster fat client is preferred
  • Use asynchronous calls, but many complex ecysystem of async calls & promises could lead into callback hell
  • Leverage multi threading. Interesting!
Web Workers solve this issue by providing capability of multi threading in JavaScript.  
 
Let's define a problem first and solve for that.

Problem statement

If you run a heavy JavaScript script then the UI becomes unresponsive and throws the following error. This is a very common problem “Unresponsive script” that we all have seen some day or the other.

Unresponsive script error

I've written sample code with a problem and the solution also.

Index.html
  1. <!DOCTYPE html>  
  2. <html>  
  3. <head>  
  4.     <title>Unresponsive dialog problem</title>  
  5. </head>  
  6. <body>  
  7. <input id="num">  
  8. <button id="btnWordsSol">Get Words - Solution</button>  
  9. <button id="btnWordsPrb">Get Words - Problem</button>  
  10. <button id="btnTerminate">Terminate</button>  
  11. <button id="btnClear">Clear</button><br><br>  
  12. <div id="msg"></div>  
  13. <script type="text/javascript" src="js/vendor/jquery-1.10.2.min.js"></script>  
  14. <script type="text/javascript">  
  15.     var worker=new Worker('js/solutions.js');  
  16.     $('#btnClear').click(function() {  
  17.         $('#msg').text('');  
  18.     });  
  19.   
  20.     $('#btnTerminate').click(function() {  
  21.         $('#msg').text('');  
  22.         if(worker)  
  23.         {  
  24.             worker.terminate();  
  25.             worker=null;  
  26.             $('#msg').text('worker destroyed');  
  27.             $('#prg').val(0)        ;  
  28.         }  
  29.     });  
  30.     $('#btnWordsSol').click(function() {  
  31.         $('#msg').text('');  
  32.   
  33.         var num=$('#num').val();  
  34.         if(worker == null)  
  35.             worker=new Worker('solution.js');  
  36.         worker.postMessage(num);  
  37.         worker.onmessage=function(event) {  
  38.             if(event.data.percent!=null)  
  39.                 $('#prg').val(event.data.percent)  
  40.             $('#msg').text(event.data.showText);  
  41.         }  
  42.   
  43.         worker.onerror=function(event) {  
  44.             $('#msg').text(event.lineno+ ' : '+ event.message);  
  45.         }  
  46.     });  
  47.   
  48.     $('#btnWordsPrb').click(function() {  
  49.         $('#msg').text('');  
  50.   
  51.         var text1="Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore t dolore magna aliqua. Ut enim ad minim veniam,quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodoconsequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat nonproident, sunt in culpa qui officia deserunt mollit anim id est laborum.";  
  52.         var arrOfWords=text1.split (" ");  
  53.         // console.log('length of our para: '+arrOfWords.length)    ;  
  54.         var tempStr="";  
  55.         var index;  
  56.         var num=$('#num').val();  
  57.         for (var i = 1; i <= num; i++) {  
  58.             indexMath.floor(Math.random()*arrOfWords.length);  
  59.             tempStr+=" "+arrOfWords[index];  
  60.         }  
  61.         $('#msg').text(tempStr);  
  62.   
  63.     });  
  64. </script>  
  65.   
  66. </body>  
  67. </html> 

Download this code and run this in the webserver, for example http://localhost/webworker/index.html

It looks like this:

Unresponsive dilog problem1

Enter any number, say 100 in the text box and click “Get Words – Problem” and see the output of random words, for example:

Unresponsive dilog problem2
This button will work if you want to generate the number of words in the text box, say 1000000 (it may be less at your machine). But if you enter a higher value, say 10000000, then after clicking “Get Words – Problem” it'll throw the following message after some time:

warning Unresponsive script

Solution

To solve these problems, let's leverage the WW provided by HTML5. Enter the same number in the text box 10000000 and click “Get Words – Solution” and it'll work. We'll review Web Worker life cycle, i.e.,
  • spawning a worker (installing, activate)
  • idle
  • post message
  • terminate
  • error

 
Spawning a worker
 
To create a Web Worker is simple and you need a new JavaScript file which contains code that you want WebWorker to execute. Hence, below code will create a new object from Worker.
  1. $('#btnWordsSol').click(function()  
  2.   
  3. var worker=new Worker('solution.js');  
Communicating with a Web Worker
 
To use these Web Workers in real world you need to establish a communication. These messages could be simple string, objects. The preceding line will load the script located at “worker.js” and execute it in the background. You need to call the Worker() constructor with the URI of a script to execute in the Worker thread as in the following:
  1. worker.postMessage(num);  
  2.   
  3.  worker.onmessage=function(event) {  
  4.    if(event.data.percent!=null)  
  5.     $('#prg').val(event.data.percent)  
  6.    $('#msg').text(event.data.showText);  
  7.   } 
If you want to get data from the Worker (for example, the output of the processed information, notifications, and so on) then you should set the Worker's onmessage property to an appropriate event handler function. The onmessage event is callback that receives the value from the background JavaScript. It also sends data back via postMessage.
 
Capturing error
 
Web Workers support onerror event. The onerror event captures a JavaScript error that occurs in the background JavaScript.
  1. worker.onerror=function(event) {  
  2.   $('#msg').text(event.lineno+ ' : '+ event.message);  
  3.  }  
If you want to terminate WW in between then you can click the Terminate button that will kill between the worker processes as in the following:

worker.terminate();

terminate WW

You can use self or this keyword to access onmessage. It's sending data back to problem.html via self.postMessage.

So, communication between the foreground thread running problem.html and the background thread running solution.js is done via postMessage.

Theory of WW

WW runs JavaScript via background thread. Don't be surprised, JavaScript now has background thread execution capability. We know JavaScript is single-threaded and scripts execute sequentially.

But, it's a real thread, spawned by the OS, that executes in the background.

Features available to WW

Due to the multi-threaded behavior, a WW only has access to the following subset of JavaScript features:
  • the navigator object
  • the location object
  • XMLHttpRequest
  • setTimeOut/clearTimeOut/setInterval/clearInterval
  • importScripts
  • spawning other WW

Limitations of WW

It has some limitations apart from it's multithreading advantage, i.e., it can't access:

  • DOM elements
  • window object
  • document object
  • parent object
Types WW

Primarily there are three dedicated, shared and inline workers. What we've seen above is an example of dedicated. I'll post another blog for shared and inline in detail and some more interesting features. Keep watching this space.

Please share feedback.