33

We can use the web worker in HTML5 like this:

var worker = new Worker('worker.js');

but why can't we call a function like this?

var worker = new Worker(function(){
    //do something
});
Qix - MONICA WAS MISTREATED
  • 12,202
  • 13
  • 73
  • 131
Dozer
  • 4,319
  • 11
  • 33
  • 52
  • 2
    Another plugin which can help you is [vkThread](http://www.eslinstructor.net/vkthread/). Take a look at [http://www.eslinstructor.net/vkthread/](http://www.eslinstructor.net/vkthread/) You'll find examples and documentation there. – vadimk Sep 16 '13 at 06:53
  • You need to use a URL but some browsers will allow you to use a BLOB URL. See [this](http://stackoverflow.com/a/6454685/19676) answer for an example. – Maurice Jul 06 '12 at 06:56
  • As @Qix said in another reponses comments, vkThread is also based on function.toString, which brings inconsistency between browsers. – Stranded Kid Jun 18 '15 at 09:30

7 Answers7

23

This is the way web workers are designed. They must have their own external JS file and their own environment initialized by that file. They cannot share an environment with your regular global JS space for multi-threading conflict reasons.

One reason that web workers are not allowed direct access to your global variables is that it would require thread synchronization between the two environments which is not something that is available (and it would seriously complicate things). When web workers have their own separate global variables, they cannot mess with the main JS thread except through the messaging queue which is properly synchronized with the main JS thread.

Perhaps someday, more advanced JS programmers will be able to use traditional thread synchronization techniques to share access to common variables, but for now all communication between the two threads must go through the message queue and the web worker cannot have access to the main Javascript thread's environment.

jfriend00
  • 580,699
  • 78
  • 809
  • 825
  • agreed, we are some time away but have come along way. – Shawn E Carter Jul 06 '12 at 02:23
  • I think the javascript is Single-threaded.But the broswer can open and run pages with Multi-threaded. So I guess the web worker create one new page(or call it a new environment ) and run the script in it? So the web worker can just call an external JS file. – Dozer Jul 06 '12 at 02:42
  • @Dozer - that's basically correct. The main javascript thread is single threaded and much javascript code on the web assumes that. Rather than add advanced thread synchronization tools to allow generic multithreading, those who designed web workers decided to take a smaller step and allow a new thread, but ONLY if it initializes it's own global environment via a new JS file and cannot share any global access with the main thread except when synchronized through the message queue. This prevents global variable synchronization issues between threads. – jfriend00 Jul 06 '12 at 02:51
  • I tested my guess,and it was incorrect. Run js in in page made by window.open() will alse block the thread. – Dozer Jul 06 '12 at 08:15
10

This question has been asked before, but for some reason, the OP decided to delete it.
I repost my answer, in case one needs a method to create a Web worker from a function.


In this post, three ways were shown to create a Web worker from an arbitrary string. In this answer, I'm using the third method, since it's supported in all environments.

A helper file is needed:

// Worker-helper.js
self.onmessage = function(e) {
    self.onmessage = null; // Clean-up
    eval(e.data);
};

In your actual Worker, this helper file is used as follows:

// Create a Web Worker from a function, which fully runs in the scope of a new
//    Worker
function spawnWorker(func) {
    // Stringify the code. Example:  (function(){/*logic*/}).call(self);
    var code = '(' + func + ').call(self);';
    var worker = new Worker('Worker-helper.js');
    // Initialise worker
    worker.postMessage(code);
    return worker;
}

var worker = spawnWorker(function() {
    // This function runs in the context of a separate Worker
    self.onmessage = function(e) {
        // Example: Throw any messages back
        self.postMessage(e.data);
    };
    // etc..
});
worker.onmessage = function() {
    // logic ...
};
worker.postMessage('Example');

Note that the scopes are strictly separated. Variables can only be passed and forth using worker.postMessage and worker.onmessage. All messages are structured clones.

Community
  • 1
  • 1
Rob W
  • 315,396
  • 71
  • 752
  • 644
  • This assumes `Function.toString()` works as an un-eval decompiler, which is NOT the case in certain environments. Use with caution! – Qix - MONICA WAS MISTREATED Nov 03 '14 at 22:52
  • @Qix Your comment would be more helpful if you can name at least one common browser where Web Workers are supported, but `toString()` on a user-defined function does not return a functionally equivalent source by default. – Rob W Nov 03 '14 at 23:18
  • Browsers are not the only Javascript environments. The fact of the matter is that this is against the W3C standard, as well as the Ecmascript standards (functions are not supposed to be decompiled; environments that support it provide it as an added feature for debugging purposes only). – Qix - MONICA WAS MISTREATED Nov 04 '14 at 02:47
  • @Qix Function serialization does not violate any standard (and certainly *not* a w3 standard, since W3 is not responsible for publishing JS standards...). Specifically, the [current ECMAScript specification](http://es5.github.io/#x15.3.4.2) states that the representation is implementation-dependent. And in practice, all modern JavaScript engines return a functionally equivalent piece of JavaScript code for user-defined functions. If you want to prove your point, name one non-esoteric JavaScript environment that supports Web Workers but not serialization of functions. – Rob W Nov 04 '14 at 13:31
  • 1
    I stated using functions in the way you are suggesting for web workers is a hack and isn't covered in the standard, thus is prone to subtle bugs or undefined behavior. As well, you can't assume every environment is going to support something not in the standard. I was simply pointing this out. – Qix - MONICA WAS MISTREATED Nov 04 '14 at 14:09
3

This answer might be a bit late, but I wrote a library to simplify the usage of web workers and it might suit OP's need. Check it out: https://github.com/derekchiang/simple-worker

It allows you to do something like:

SimpleWorker.run({
  func: intensiveFunction,
  args: [123456],
  success: function(res) {
    // do whatever you want
  },
  error: function(err) {
    // do whatever you want
  }
})
Derek Chiang
  • 2,994
  • 5
  • 22
  • 33
  • This looks quite similar to @vadimk's vkThread post above - obviously different projects, however I can say that vkThread works well and has several different methods for use. – Nick Oct 17 '13 at 10:43
1

While it's not optimal and it's been mentioned in the comments, an external file is not needed if your browser supports blobURLs for Web Workers. HTML5Rocks was the inspiration for my code:

function sample(e)
{
    postMessage(sample_dependency());
}

function sample_dependency()
{
    return "BlobURLs rock!";
}

var blob = new Blob(["onmessage = " + sample + "\n" + sample_dependency]);
var blobURL = window.URL.createObjectURL(blob);
var worker = new Worker(blobURL);

worker.onmessage = function(e)
{
    console.log(e.data);
};

worker.postMessage("");

Caveats:

  • The blob workers will not successfully use relative URLs. HTML5Rocks link covers this but it was not part of the original question.

  • People have reported problems using Blob URLs with Web Workers. I've tried it with IE11 (whatever shipped with FCU), MS Edge 41.16299 (Fall Creator's Update), Firefox 57, and Chrome 62. No clue as to Safari support. The ones I've tested have worked.

  • Note that "sample" and "sample_dependency" references in the Blob constructor call implicitly call Function.prototype.toString() as sample.toString() and sample_dependency.toString(), which is very different than calling toString(sample) and toString(sample_dependency).

Posted this because it's the first stackoverflow that came up when searching for how to use Web Workers without requesting an additional file.

Took a look at Zevero's answer and the code in his repo appears similar. If you prefer a clean wrapper, this is approximately what his code does.

Lastly -- I'm a noob here so any/all corrections are appreciated.

lmcdo
  • 35
  • 8
1

WebWorkers Essentials

WebWorkers are executed in an independent thread, so have no access to the main thread, where you declare them (and viceversa). The resulting scope is isolated, and restricted. That's why, you can't , for example, reach the DOM from inside the worker.


Communication with WebWorkers

Because communication betwen threads is neccessary, there are mechanisms to accomplish it. The standard communication mechanism is through messages, using the worker.postMessage() function and the worker.onMessage(), event handler.

More advanced techniques are available, involving sharedArrayBuffers, but is not my objective to cover them. If you are interested in them, read here.


Threaded Functions

That's what the standard brings us. However, ES6 provides us enough tools, to implement an on-demmand callable Threaded-Function.

Since you can build a Worker from a Blob, and your Function can be converted into it (using URL.createObjectURL), you only need to implement some kind of Communication Layer in both threads, to handle the messages for you, and obtain a natural interaction.

Promises of course, are your friend, considering that everything will happen asynchronously.

Applying this theory, you can implement easilly, the scenario you describe.


My personal approach : ParallelFunction

I've recently implemented and publised a tiny library wich does exactly what you describe. in less than 2KB (minified).

It's called ParallelFunction, and it's available in github, npm , and a couple of CDNs.

As you can see, it totally matches your request:

// Your function...
let calculatePi = new ParallelFunction( function(n){
    // n determines the precision , and in consequence 
    // the computing time to complete      
    var v = 0;
    for(let i=1; i<=n; i+=4) v += ( 1/i ) - ( 1/(i+2) );
    return 4*v;
});

// Your async call...
calculatePi(1000000).then( r=> console.log(r) );

// if you are inside an async function you can use await...
( async function(){    
    let result = await calculatePi(1000000);
    console.log( result );
})()

// once you are done with it...
calculatePi.destroy();

After initialization, you can call your function as many times you need. a Promise will be returned, wich will resolve, when your function finishes execution.

By the way, many other Libraries exists.

colxi
  • 5,010
  • 1
  • 30
  • 36
0

By design web workers are multi-threaded, javascript is single threaded"*"multiple scripts cannot run at the same time.

refer to: http://www.html5rocks.com/en/tutorials/workers/basics/

Shawn E Carter
  • 4,170
  • 1
  • 19
  • 39
0

Just use my tiny plugin https://github.com/zevero/worker-create

and do

var worker_url = Worker.create(function(e){
  self.postMessage('Example post from Worker'); //your code here
});
var worker = new Worker(worker_url);
zevero
  • 1,834
  • 18
  • 11