I recently had my first experience deploying code using Web Workers. I’ve known about workers for a long time and have written some toy examples, but until now, I haven’t used them in any code that would be run by other people. The biggest reason for this has been the lack of support by Internet Explorer. When this project came along, I decided that Web Workers would be a good fit because only a few people would be running this code, and they agreed that it wouldn’t be a problem to use a different browser. So, Web Workers it was!
About the Project
Without going into too many details, I’d like to describe the project, and why I thought Web Workers were appropriate. The purpose of this site is to collect survey results from a few hundred people every three to four months. At the end of the collection period, the site admin must manually open each survey and print the results. Doing this for hundreds of surveys can easily consume hours of work. The admin approached me and asked if I could implement a batch print function that would allow multiple surveys to be selected, concatenated in a single window, and printed.
Theoretically, all of the surveys could be returned by a single dynamic page, but due to the long wait time the connection would timeout before all of the surveys could be processed. This led me to the decision to use AJAX calls. Each survey can take several seconds to load, so it was important to keep the user interface responsive while hundreds of surveys downloaded. This shouldn’t have been a problem since the A in AJAX stands for asynchronous, but there was a catch. When the user selects the surveys to print, they are in alphabetical order. By making a bunch of asynchronous calls, it was possible that the surveys could be returned out of order. My decision was to make synchronous AJAX calls (synchronous asynchronous calls?) from a Web Worker. As each AJAX call was satisfied, the Web Worker would post a message to the main thread to update the HTML on the page (since workers cannot access the DOM).
The Source Code
The HTML source is listed below. This is a modified version of the original file. I have removed some details and references to the site’s owner. I haven’t tested the modified version, so there is a chance I broke something in the process.
The window’s load event handler creates a single Web Worker to download all of the surveys. The worker is defined in a file named batch-print-worker.js, which will be covered later. The identification numbers of all of the surveys to be printed are passed as an array to the worker. Once the worker receives the identification numbers, the download process is started. The worker then updates the page by posting message back to the main thread. The worker utilizes three types of custom messages, which are described below.
- survey ― Each “survey” type message represents a single downloaded survey. All of the surveys are concatenated in the “surveys” <span> element.
- status ― The “status” message is used to keep a running count of the number of surveys that have been processed thus far. This allows the user to see how much work has been done, and how much remains. This progress is displayed in the HTML <span> element named “status”.
- done ― Once all of the surveys have been downloaded, the worker posts a single “done” message. Since we’re not interested in printing the status information, the “done” message is used to hide the status.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Batch Print Job</title>
<meta charset="UTF-8" />
<script>
window.addEventListener("load", function() {
var worker = new Worker("batch-print-worker.js");
var request = {};
worker.addEventListener("message", function(event) {
var status = document.getElementById("status");
var surveys = document.getElementById("surveys");
var type = event.data.type;
var text = event.data.text;
if (type === "status")
status.innerHTML = text;
else if (type === "survey")
surveys.innerHTML += text;
else if (type === "done")
status.style.display = "none";
});
request.eids = [1, 2, 3]; // pass an array of requested survey identifiers
worker.postMessage(request);
});
</script>
</head>
<body>
<span id="status"></span>
<span id="surveys"></span>
</body>
</html>
The Web Worker
As I mentioned, the worker is defined in the file batch-print-worker.js. The contents of that file are shown below. Again, this code has been modified from its original version, and may have been broken in the process. The worker’s “message” event handler loops over all of the requested survey identification numbers. For each survey, the worker first posts a “status” update message to the main thread. Next, the survey is downloaded via AJAX and posted back to the main thread via a “survey” message. After all of the surveys have been processed, a “done” message is posted to the main thread, and the worker shuts down.
self.addEventListener("message", function(event) {
var eids = event.data.eids; // array of the survey id numbers
var msg = {};
for (var i = 0, len = eids.length; i < len; i++) {
var http = new XMLHttpRequest();
var url = "survey_page_url" + eids[i]; // url of the specific survey
// tell the main thread what survey is being processed
msg.type = "status";
msg.text = "Processing evaluation " + (i + 1) + " of " + len + "<hr />";
self.postMessage(msg);
// make the AJAX call and return the HTML to the main thread
http.open("GET", url, false);
http.send(null);
msg.type = "survey";
msg.text = http.responseText;
self.postMessage(msg);
}
// tell the main thread that we're done and close
msg.type = "done";
msg.text = "";
self.postMessage(msg);
self.close();
});
Conclusion
Web Workers are a simple, yet powerful way to achieve task level parallelism in HTML5. Once Internet Explorer adds support for Web Workers, I will definitely be using them more often. Be on the watch for another post or two covering this topic. By the way, the solution provided in this post was well received by the site administrator.


