How to create a Web Worker from a string

40,828

Solution 1

Summary

  • blob: for Chrome 8+, Firefox 6+, Safari 6.0+, Opera 15+
  • data:application/javascript for Opera 10.60 - 12
  • eval otherwise (IE 10+)

URL.createObjectURL(<Blob blob>) can be used to create a Web worker from a string. The blob can be created using the BlobBuilder API deprecated or the Blob constructor.

Demo: http://jsfiddle.net/uqcFM/49/

// URL.createObjectURL
window.URL = window.URL || window.webkitURL;

// "Server response", used in all examples
var response = "self.onmessage=function(e){postMessage('Worker: '+e.data);}";

var blob;
try {
    blob = new Blob([response], {type: 'application/javascript'});
} catch (e) { // Backwards-compatibility
    window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder;
    blob = new BlobBuilder();
    blob.append(response);
    blob = blob.getBlob();
}
var worker = new Worker(URL.createObjectURL(blob));

// Test, used in all examples:
worker.onmessage = function(e) {
    alert('Response: ' + e.data);
};
worker.postMessage('Test');

Compatibility

Web workers are supported in the following browsers source:

  • Chrome 3
  • Firefox 3.5
  • IE 10
  • Opera 10.60
  • Safari 4

This method's support is based on the support of the Blob API and the URL.createObjectUrl method. Blob compatibility:

  • Chrome 8+ (WebKitBlobBuilder), 20+ (Blob constructor)
  • Firefox 6+ (MozBlobBuilder), 13+ (Blob constructor)
  • Safari 6+ (Blob constructor)

IE10 supports MSBlobBuilder and URL.createObjectURL. However, trying to create a Web Worker from a blob:-URL throws a SecurityError.

Opera 12 does not support URL API. Some users may have a fake version of the URL object, thanks to this hack in browser.js.

Fallback 1: data-URI

Opera supports data-URIs as an argument to the Worker constructor. Note: Do not forget to escape special characters (Such as # and %).

// response as defined in the first example
var worker = new Worker('data:application/javascript,' +
                        encodeURIComponent(response) );
// ... Test as defined in the first example

Demo: http://jsfiddle.net/uqcFM/37/

Fallback 2: Eval

eval can be used as a fallback for Safari (<6) and IE 10.

// Worker-helper.js
self.onmessage = function(e) {
    self.onmessage = null; // Clean-up
    eval(e.data);
};
// Usage:
var worker = new Worker('Worker-helper.js');
// `response` as defined in the first example
worker.postMessage(response);
// .. Test as defined in the first example

Solution 2

I agree with the current accepted answer but often editing and managing the worker code will be hectic as its in the form of a string.

So optionally we can use the below approach where we can keep the worker as a function, and then covert to string->blob:

// function to be your worker
function workerFunction() {
    var self = this;
    self.onmessage = function(e) {
        console.log('Received input: ', e.data); // message received from main thread
        self.postMessage("Response back to main thread");
    }
}


///////////////////////////////

var dataObj = '(' + workerFunction + ')();'; // here is the trick to convert the above fucntion to string
var blob = new Blob([dataObj.replace('"use strict";', '')]); // firefox adds "use strict"; to any function which might block worker execution so knock it off

var blobURL = (window.URL ? URL : webkitURL).createObjectURL(blob, {
    type: 'application/javascript; charset=utf-8'
});


var worker = new Worker(blobURL); // spawn new worker

worker.onmessage = function(e) {
    console.log('Worker said: ', e.data); // message received from worker
};
worker.postMessage("some input to worker"); // Send data to our worker.

This is tested in IE11+ and FF and Chrome

Solution 3

The accepted answer is a bit complex, due to supporting backwards compatibility, so I wanted to post the same thing but simplified. Try this in your (modern) browser console:

const code = "console.log('Hello from web worker!')"
const blob = new Blob([code], {type: 'application/javascript'})
const worker = new Worker(URL.createObjectURL(blob))
// See the output in your console.

Solution 4

I've made an approach with most of your ideas and adding some of mine. The only thing my code needs on worker is to use 'this' to refer 'self' scope. I'm pretty sure that this is very improvable:

// Sample code
var code = function() {
    this.onmessage = function(e) {
        this.postMessage('Worker: '+e.data);
        this.postMessage('Worker2: '+e.data);
    };
};

// New thread worker code
FakeWorkerCode = function(code, worker) {
    code.call(this);
    this.worker = worker;
}
FakeWorkerCode.prototype.postMessage = function(e) {
    this.worker.onmessage({data: e});
}
// Main thread worker side
FakeWorker = function(code) {
    this.code = new FakeWorkerCode(code, this);
}
FakeWorker.prototype.postMessage = function(e) {
    this.code.onmessage({data: e});
}

// Utilities for generating workers
Utils = {
    stringifyFunction: function(func) {
        // Stringify the code
        return '(' + func + ').call(self);';
    },
    generateWorker: function(code) {
        // URL.createObjectURL
        windowURL = window.URL || window.webkitURL;   
        var blob, worker;
        var stringified = Utils.stringifyFunction(code);
        try {
            blob = new Blob([stringified], {type: 'application/javascript'});
        } catch (e) { // Backwards-compatibility
            window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder;
            blob = new BlobBuilder();
            blob.append(stringified);
            blob = blob.getBlob();
        }

        if ("Worker" in window) {
            worker = new Worker(windowURL.createObjectURL(blob));
        } else {
            worker = new FakeWorker(code);
        }
        return worker;
    }
};

// Generate worker
var worker = Utils.generateWorker(code);
// Test, used in all examples:
worker.onmessage = function(e) {
    alert('Response: ' + e.data);
};
function runWorker() {
    worker.postMessage('working fine');
}

Demo: http://jsfiddle.net/8N6aR/

Solution 5

Nice answer - I've been working on a similar problem today when trying to create Web Workers with fallback capabilities when they're not available (i.e. run worker script in main thread). As this thread is pertains to the topic, I thought I'd provide my solution here:

    <script type="javascript/worker">
        //WORKER FUNCTIONS
        self.onmessage = function(event) {
            postMessage('Hello, ' + event.data.name + '!');
        }
    </script>

    <script type="text/javascript">

        function inlineWorker(parts, params, callback) {

            var URL = (window.URL || window.webkitURL);

            if (!URL && window.Worker) {

                var worker = new window.Worker(URL.createObjectURL(new Blob([parts], { "type" : "text/javascript" })));

                worker.onmessage = function(event) {
                  callback(event.data);
                };

                worker.postMessage(params);

            } else {

                var postMessage = function(result) {
                  callback(result);
                };

                var self = {}; //'self' in scope of inlineWorker. 
                eval(parts); //Converts self.onmessage function string to function on self via nearest scope (previous line) - please email [email protected] if this could be tidier.
                self.onmessage({ 
                    data: params 
                });
            }
        }

        inlineWorker(
            document.querySelector('[type="javascript/worker"]').textContent, 
            {
                name: 'Chaps!!'
            },
            function(result) {
                document.body.innerHTML = result;
            }
        );

    </script>
</body>

Share:
40,828
bigblind
Author by

bigblind

to learn more about me check out my blog

Updated on February 19, 2022

Comments

  • bigblind
    bigblind about 2 years

    How can I use create a Web worker from a string (which is supplied via a POST request)?

    One way I can think of, but I'm not sure how to implement it, is by creating a data-URI from the server response, and passing that to the Worker constructor, but I've heard that some browsers don't allow this, because of the same origin policy.

    MDN states the uncertainty about the origin policy around data URI's:

    Note: The URI passed as parameter of the Worker constructor must obey the same-origin policy. There is currently disagreement among browsers vendors on whether data URIs are of the same-origin or not; Gecko 10.0 (Firefox 10.0 / Thunderbird 10.0) and later do allow data URIs as a valid script for workers. Other browsers may disagree.

    Here's also a post discussing it on the whatwg.

  • Rob W
    Rob W almost 12 years
    @BrianFreid Thanks for your edit, but it's not needed. If you look a few lines further, you'll see "IE10 supports MSBlobBuilder and URL.createObjectURL. However, trying to create a Web Worker from a blob:-URL throws a SecurityError.". So, adding MSBlobBuilder will have no effect, the only option is fallback #2.
  • gsnedders
    gsnedders about 11 years
    Opera 12 no longer defines URL (and hence neither defines any properties on it), and the Blob constructor is nowadays well enough supported.
  • Benjamin Gruenbaum
    Benjamin Gruenbaum almost 11 years
    I have verified that this still happens in IE11, at least in the preview.
  • jayarjo
    jayarjo over 9 years
    Are dataURIs supported only in Opera or in all other browsers (except IE) as well?
  • jayarjo
    jayarjo over 9 years
    Also new Function maybe more performative than eval (at least it is - in non-IE browsers).
  • Rob W
    Rob W over 9 years
    @jayarjo data:-URIs for Web Workers are also supported in Firefox, but not in Chrome or Opera 15+. The performance of eval is not relevant, you're not going to create millions of Web workers per second.
  • t1nk
    t1nk almost 9 years
    self.close() and worker.terminate() kill IE11 - 'Discontinued operation of the program "Internet Explorer". How to get around a bug?
  • puppeteer701
    puppeteer701 almost 9 years
    is there a way to do it with Blob in IE 10?
  • Rob W
    Rob W almost 9 years
    @MatjažJurečič AFAIK, no.
  • puppeteer701
    puppeteer701 almost 9 years
    I would just love it, if Microsoft would drop dead!
  • Blue
    Blue over 7 years
    @SenJacob As this is not a community wiki post, you should expose potential issues to the poster via a comment instead of an edit.
  • Sen Jacob
    Sen Jacob over 7 years
    @FrankerZ Sorry. I had to make it work in IE11 with the changes I did. @ ChanuSukarno Could you please check whether the changes in the revision 3 is OK?
  • Stefan Reich
    Stefan Reich about 6 years
    Am I wrong in assuming that window.URL is set, but never used? (only "URL" is used)
  • Sora2455
    Sora2455 almost 5 years
    FYI, the "type: 'application/javascript; charset=utf-8'" belongs in the Blob constructor, not the createObjectURL call.
  • ADJenks
    ADJenks about 4 years
    So... You're building a function outside of your worker, just to simply read it better in your text editor? That's ridiculous. You have to load that function into memory in two contexts for no reason.
  • Kousha
    Kousha over 2 years
    How can you use this, but passing parameters into the function workerFunction to use to generate the code with?
  • Adam Gawne-Cain
    Adam Gawne-Cain over 2 years
    @ADJenks. For me loading function into two contexts is not expensive (compared to all the other stuff my app does). Sometimes I want to run my worker function in foreground (e.g. for synchronous return) so I will often need to load my function in two contexts anyway. Also, the trick to stringily function works through TypeScript and WebPack processing which is a big win for me.