You Betta Work: Enter the Web Worker

I’m one of those people that thinks JavaScript is fascinating and beautiful. I love typing prototype to add a method to an object. Just kidding. Seriously though, JavaScript can be implemented in the most fascinating ways. Through my web-development journey, I have come across this ‘web-worker’ guy several times. For those that are curious as me, I’ve done a bit of research and collected some interesting information about what exactly web-workers do and why you should care.

Fundamentals

Firstly, to understand what web-workers are doing, it’s important to understand what basic JavaScript running in your browser is doing. First point:

  • Javascript is embedded in your browser.

I’ll assume the reader has a bit of familiarity with JavaScript. It’s implemented in a browser to give functionality to a web page especially when static elements like CSS and HTML might need to be animated or interactive.

When Javascript is loaded on a page, it is actually placed into the HTML in a ‘<script>’ tag. If I use a script tag and label the source as another file in my assets, it will render the page with the JavaScript inside those brackets. Tangentially related, this embedded code is interpreted by the browser which then compiles the script in byte code. (Let’s avoid going down the wormhole of interpreted/compiled–it’s a bit of both, am I right?) The script that one sees in the source code of their page is handled as the main UI thread. This is the main execution process. It will register an on-click event. It has access to elements on the page and can make minor calculations. This brings us to the second point:

Limitations of the Main thread

This error message.

When your browser is running a script and the computation takes too long, it catches the error with this alert. The browser cannot tell the difference between a script that is in an infinite loop and one that is simply taking too long. A certain time interval is set as the threshold. Why don’t we just test the script to see if it is in an infinite loop? Oh wait–halting problem.

So, we have one main thread in the browser that can only run short calculations. But, say we want to render a 3D environment and simulate physics in that environment? That’s some heavy computation! What if I want to find a really big prime number?

Enter the web-worker

The web worker is a script that runs independent of the main thread’s event queue. It can handle large processes that would normally time out if they were run as a user interface script. With a web worker, I could make the aforementioned calculations independent of what events are going on in the window.

The way it works:

  • The main thread creates the web worker object as well as a web worker object to be sent input from main.
1
 var worker = new Worker('worker.js');
  • The main thread receives output from the web worker using ‘.onmessage’.
1
2
3
4
worker.onmessage = function(event) {
  console.log(event.data);
  // puts the output to the console whenever it is sent
}
  • The main thread sends input to the web worker through ‘.postMessage’.
1
2
3
function sendInput(input) {
  worker.postMessage(input);
}

In the main thread, we have to specify which worker we are sending the input and output to; however, the web worker need only say that it is receiving or sending information. This is because the worker is linked to its origin of creation. If I want the dedicated worker to send and receive information, I simply call the function postMessage() or onmessage().

To be specific, we are talking about dedicated web workers. They have a global scope that allows you to implicity call a MessagePort object with postMessage and onmessage. This is not the case for all web workers. A shared web worker has a SharedWorkerGlobalScope, which allows the worker to receive input from multiple sources through an ‘onconnect’ event. This functionality allows for separate windows in the browser to connect to the same web worker.

While workers allow us to run heavy computation independent of the event queue, there is a trade-off. If you try accessing DOM, window or parent objects from a worker, you will get undefined. Everything else is fair game. So, if you want heavy computation and window events, you have to interweave the workers and the main thread.

Get to Work!

I’m sure by now the reader gets a sense of what this tool could be used for. Thus far in my development journey, I have been using workers to simulate physics for a rendered 3D environment.

Physijs uses the physijs_worker to compute the physics of the scene which then posts the response back to the main thread. The formulas are actually written in ammo.js, a port from the C++ physics engine, bullet.

In physi.js, the main thread of the simulation, the worker is thus instantiated and immediately sets transferableMessage to be the postMessage function. TransferableMessage allows for sending an object completely across worker boundaries, neutralizing the original. Come back for another blog post on this functionality.

physi.js

1
2
3
4
5
6
7
this._worker = new Worker( Physijs.scripts.worker || 'physijs_worker.js' );
      this._worker.transferableMessage = this._worker.webkitPostMessage || this._worker.postMessage;

    // ...

    var ab = new ArrayBuffer( 1 );
      this._worker.transferableMessage( ab, [ab] );

The last two lines post the message to physijs_worker.js. Let’s take a look at what’s going on there.

physijs_worker.js

1
var transferableMessage = self.webkitPostMessage || self.postMessage;

Again, the script sets transferableMessage to be the function by which messages are sent back to the main thread. And later in the script, the worker sends back the same objects, updated with calculations.

1
2
var ab = new ArrayBuffer( 1 );
transferableMessage( ab, [ab] );

Sources

For further reading, check out these thorough resources on the subject:

On transferable objects: