A web worker is a JS script that runs in the background, separately from other scripts, allowing us to introduce threading in our web apps. Although not part of the HTML5 spec, web workers can be used with HTML5 apps. In this Quick Tip, we'll take a look at how to use them.
Introduction to Web Workers
In the land of HTML5 we have some very interesting APIs available. Some of them – like Web Workers – are useful for increasing performance, which is very important for both apps and games. But how do web workers... well, work?
Every instance of a web worker creates another thread, in which your JavaScript runs. You instantiate one like so:
var worker = new Worker('filename.js');
Here, 'filename.js' is the name of file containing your script. Because Workers are individual environments, you can't use code embedded directly in HTML; you must use a separate file.
Communication: Sending and Receiving Data
Workers don't have access to the page DOM or the global object, so how do they communicate with the site? It's simple. When you want to send some data from your page to a Worker, you invoke postMessage()
.
This takes one parameter: data to send, which can be either a string or a JSON parsable object (which means that you can't pass functions or circular references, or you will get a DOM_EXCEPTION
). On some browsers there is a problem with objects, so it's always better to manually parse the object with JSON.parse()
so you don't have to worry about incomplete implementations.
The same goes if you are sending data from a Worker to the page: just invoke postMessage()
on self
, which refers to the Worker's global scope. (You do this inside the Worker's script, of course).
Then, to receive the data you have to attach an onmessage
event handler. There are two ways of doing that, just like with regular events for DOM elements; you can either directly assign some function to the Worker's onmessage
property, or you can use addEventListener()
.
// First way: worker.onmessage = function (e) { console.log(e.data); // Log the data passed } // Second way: worker.addEventListener('message', function (e) { console.log(e.data); // Log the data passed });
It's your choice which method to use. Either way, the function's parameter will be an event
object, and the data you sent using postMessage()
will be passed via the data
property of this event.
External Scripts and Libraries
Okay, but what if we have to use some external library? We don't have access to the DOM or the global scope, so we can't just inject the script.
Of course we don't need to – there is a function for that. It is called importScripts()
and it accepts one or more arguments: script names to load inside the scope of the Worker. You should be aware that scripts passed into this function are loaded in a random order, but they will be executed as specified and script execution will be paused until they are loaded.
importScripts('one-lib.js'); // Loads one script importScripts('first-lib.js', 'second-lib.js', 'third-lib.js'); // Loads three scripts
You can use importScripts
anywhere in your code, making it easy to create JSONP requests inside the Workers, as we will do in the following example.
Example: Workers in Action
Right, so now you probably want to see a Worker in action. Instead of showing something fairly useless, like obtaining primes or Fibonacci numbers, I've decided to make something that you will maybe use after a few changes.
The example script (I've included the Worker's code only, the rest is easy to do) will get the last 100 Tweets from @envatoactive (we need to set the count to 121 instead of 100, as Tweeter API is sending fewer tweets than requested – don't ask me why, I don't know).
Here's the code that would go inside the actual Web Worker script file:
// Helper function for processing the data var process = function (data) { // Iterate through the data; we know it's an array, so it's safe for (var i = 0, v; v = data[i]; i++) { // And pass Tweet's text to the page self.postMessage({ text: v.text }); } // After work is done, let the page know self.postMessage("finished"); } // Attach event listener to handle messages self.addEventListener('message', function (event) { // Check if command sent was 'start' // Not necessary here, but may be useful later if (event.data == "start") { // Reply to the page that we started the work self.postMessage("started"); // Core of the script, get the Tweets // The callback parameter specifies the function to execute after the request is done // (We call the process() function, defined above.) // Count needs to be 121 because Tweeter API is sending lower amount of data than requested importScripts("http://twitter.com/statuses/user_timeline/envatoactive.json?callback=process&count=121"); } });
It should be easy to understand how this all works from the comments. This lets your app load all the tweets in the background, using a separate thread.
Now try inserting the following equivalent code, which doesn't use web workers, into the head of an empty page instead, and note the delay. (It's still small, but imagine if you were getting not 100 but 100,000 Tweets):
<script type="text/javascript"> var process = function (data) { // Iterate through the data; we know it's an array, so it's safe for (var i = 0, v; v = data[i]; i++) { // And log Tweet's text to the console console.log(v.text); } } </script> <script src="http://twitter.com/statuses/user_timeline/envatoactive.json?callback=process&count=121"></script>
Conclusion
As you can see, web workers offer you a simple way to remove lag from your GUI and move complicated calculations or networking to separate threads.
I hope you learned something new from this article - maybe you will use Workers in your next project? If you have any questions or problems please comment below.
Comments