Web workers without a separate Javascript file?

Web workers without a separate Javascript file?

As far as I can tell, web workers need to be written in a separate JavaScript file, and called like this:
new Worker(‘longrunning.js’)

I’m using the closure compiler to combine and minify all my JavaScript source code, and I’d rather not have to have my workers in separate files for distribution. Is there some way to do this?
new Worker(function() {
//Long-running work here
});

Given that first-class functions are so crucial to JavaScript, why does the standard way to do background work have to load a whole ‘nother JavaScript file from the server?

Solutions/Answers:

Solution 1:

http://www.html5rocks.com/en/tutorials/workers/basics/#toc-inlineworkers

What if you want to create your worker script on the fly, or create a self-contained page without having to create separate worker files? With Blob(), you can “inline” your worker in the same HTML file as your main logic by creating a URL handle to the worker code as a string

Full example of BLOB inline worker:

<!DOCTYPE html>
<script id="worker1" type="javascript/worker">
  // This script won't be parsed by JS engines because its type is javascript/worker.
  self.onmessage = function(e) {
    self.postMessage('msg from worker');
  };
  // Rest of your worker code goes here.
</script>
<script>
  var blob = new Blob([
    document.querySelector('#worker1').textContent
  ], { type: "text/javascript" })

  // Note: window.webkitURL.createObjectURL() in Chrome 10+.
  var worker = new Worker(window.URL.createObjectURL(blob));
  worker.onmessage = function(e) {
    console.log("Received: " + e.data);
  }
  worker.postMessage("hello"); // Start the worker.
</script>

Solution 2:

The html5rocks solution of embedding the web worker code in HTML is fairly horrible.
And a blob of escaped JavaScript-as-a-string is no better, not least because it complicates work-flow (Closure compiler can’t operate on strings).

Related:  AngularJS multiple expressions concatenating in interpolation with a URL

Personally I really like the toString methods, but @dan-man THAT regex!

My preferred approach:

// Build a worker from an anonymous function body
var blobURL = URL.createObjectURL( new Blob([ '(',

function(){
    //Long-running work here
}.toString(),

')()' ], { type: 'application/javascript' } ) ),

worker = new Worker( blobURL );

// Won't be needing this anymore
URL.revokeObjectURL( blobURL );

Support is the intersection of these three tables:

This won’t work for a SharedWorker however, because the URL must be an exact match, even if the optional ‘name’ parameter matches. For a SharedWorker, you’ll need a separate JavaScript file.


2015 update – The ServiceWorker singularity arrives

Now there’s an even more powerful way of solving this problem.
Again, store the worker code as a function, (rather than a static string) and convert using .toString(), then insert the code into CacheStorage under a static URL of your choice.

// Post code from window to ServiceWorker...
navigator.serviceWorker.controller.postMessage(
 [ '/my_workers/worker1.js', '(' + workerFunction1.toString() + ')()' ]
);

// Insert via ServiceWorker.onmessage. Or directly once window.caches is exposed
caches.open( 'myCache' ).then( function( cache )
{
 cache.put( '/my_workers/worker1.js',
  new Response( workerScript, { headers: {'content-type':'application/javascript'}})
 );
});

There are two possible fall-backs. ObjectURL as above, or more seamlessly, put a real JavaScript file at /my_workers/worker1.js

Advantages of this approach are:

  1. SharedWorkers can also be supported.
  2. Tabs can share a single cached copy at a fixed address. The blob approach proliferates random objectURLs for every tab.
Related:  Getting a random value from a JavaScript array

Solution 3:

You can create a single JavaScript file that is aware of its execution context and can act as both a parent script and a worker. Let’s start off with a basic structure for a file like this:

(function(global) {
    var is_worker = !this.document;
    var script_path = is_worker ? null : (function() {
        // append random number and time to ID
        var id = (Math.random()+''+(+new Date)).substring(2);
        document.write('<script id="wts' + id + '"></script>');
        return document.getElementById('wts' + id).
            previousSibling.src;
    })();
    function msg_parent(e) {
        // event handler for parent -> worker messages
    }
    function msg_worker(e) {
        // event handler for worker -> parent messages
    }
    function new_worker() {
        var w = new Worker(script_path);
        w.addEventListener('message', msg_worker, false);
        return w;
    }
    if (is_worker)
        global.addEventListener('message', msg_parent, false);

    // put the rest of your library here
    // to spawn a worker, use new_worker()
})(this);

As you can see, the script contains all code for both the parent’s and the worker’s point of view, checking if its own individual instance is a worker with !document. The somewhat unwieldy script_path computation is used to accurately calculate the script’s path relative to the parent page, as the path supplied to new Worker is relative to the parent page, not the script.

Solution 4:

Using the Blob method, how about this for a worker factory:

var BuildWorker = function(foo){
   var str = foo.toString()
             .match(/^\s*function\s*\(\s*\)\s*\{(([\s\S](?!\}$))*[\s\S])/)[1];
   return  new Worker(window.URL.createObjectURL(
                      new Blob([str],{type:'text/javascript'})));
}

So you could use it like this…

var myWorker = BuildWorker(function(){
   //first line of worker
   self.onmessage(){....};
   //last line of worker
});

EDIT:

I’ve just extended this idea further to make it easier to do cross-thread communication: bridged-worker.js.

Related:  understanding the javascript global namespace and closures

EDIT 2:

The above link is to a gist I created. Someone else later turned it into an actual repo.

Solution 5:

Web workers operate in entirely separate contexts as individual Program’s.

This means that code cannot be moved from one context to another in object form, as they would then be able to reference objects via closures belonging to the other context.
This is especially crucial as ECMAScript is designed to be a single threaded language, and since web workers operate in separate threads, you would then have the risk of non-thread-safe operations being performed.

This again means that web workers need to be initialized with code in source form.

The spec from WHATWG says

If the origin of the resulting
absolute URL is not the same as the
origin of the entry script, then throw
a SECURITY_ERR exception.

Thus, scripts must be external files
with the same scheme as the original
page: you can’t load a script from a
data: URL or javascript: URL, and an
https: page couldn’t start workers
using scripts with http: URLs.

but unfortunately it doesn’t really explain why one couldn’t have allowed passing a string with source code to the constructor.

Solution 6:

a better to read way for a inline worker..

    var worker_fn = function(e) 
    {
        self.postMessage('msg from worker');            
    };

    var blob = new Blob(["onmessage ="+worker_fn.toString()], { type: "text/javascript" });

    var worker = new Worker(window.URL.createObjectURL(blob));
    worker.onmessage = function(e) 
    {
       alert(e.data);
    };
    worker.postMessage("start");