据我所知,web worker需要写在一个单独的JavaScript文件中,并像这样调用:

new Worker('longrunning.js')

我正在使用闭包编译器来合并和缩小我所有的JavaScript源代码,我不希望将我的worker放在单独的文件中进行分发。有什么办法可以做到吗?

new Worker(function() {
    //Long-running work here
});

既然一级函数对JavaScript如此重要,为什么标准的后台工作方式必须从web服务器加载整个其他JavaScript文件呢?


当前回答

@Trincot's似乎是目前为止最好的。然而,也许我们可以进一步发展它。所以我的想法是,

我们不要修改Function.prototype。 为线程操作获取函数的承诺/线程化版本。 如果需要,请确保仍然可以同步调用该函数。

因此,我们用spawn方法定义了Threadable类。一旦我们将函数设为该类的成员,那么它就是可线程的:)

class Threadable extends Function { constructor(f){ super("...as",`return ${f.toString()}.apply(this,as)`); } spawn(...as){ var code = `self.onmessage = m => self.postMessage((${this.toString()}).apply(self,m.data));`, blob = new Blob([code], {type: "text/javascript"}), wrkr = new Worker(window.URL.createObjectURL(blob)); return new Promise( (v,x) => ( wrkr.onmessage = m => (v(m.data), wrkr.terminate()) , wrkr.onerror = e => (x(e.message), wrkr.terminate()) , wrkr.postMessage(as) ) ); } } function add(...nums) { return nums.reduce((a,b) => a+b); } var addT = new Threadable(add); addT.spawn(1,2,3,4) .then(m => console.log(`Promisified thread returned ${m}`)); console.log(`Synchronous invocation of addT returned ${addT(1,2,3,4)}`);

其他回答

html5rocks在HTML中嵌入web worker代码的解决方案相当糟糕。 而一团转义的JavaScript-as-a-string也不会更好,尤其是因为它使工作流程复杂化(闭包编译器不能操作字符串)。

我个人非常喜欢toString方法,但是@dan-man THAT正则表达式!

我喜欢的方法是:

// 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 );

支持是这三个表的交集:

http://caniuse.com/#feat=webworkers http://caniuse.com/#feat=blobbuilder http://caniuse.com/#feat=bloburls

然而,这对SharedWorker不起作用,因为URL必须是精确匹配的,即使可选的'name'参数匹配。对于SharedWorker,您需要一个单独的JavaScript文件。


2015年更新——ServiceWorker奇点到来

现在有一种更有效的方法来解决这个问题。 同样,将工作代码存储为函数(而不是静态字符串),并使用. tostring()进行转换,然后将代码插入CacheStorage中您选择的静态URL下。

// 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'}})
 );
});

有两种可能的退路。ObjectURL和上面一样,或者更无缝地将一个真正的JavaScript文件放在/my_workers/worker1.js

这种方法的优点是:

还可以支持SharedWorkers。 选项卡可以在固定地址共享单个缓存副本。blob方法为每个选项卡增加随机objecturl。

另一个解决方案是将Worker包装在一个函数中,然后创建一个调用函数的blob,如下所示:

     function workerCode() {
        self.onmessage = function (e) {
          console.log("Got message from parent", e.data);
        };
        setTimeout(() => {
          self.postMessage("Message From Worker");
        }, 2000);
      }

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

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

将Adria的响应放在一个可复制粘贴的函数中,该函数适用于当前的Chrome和FF,但不适用于IE10 (worker from blob会导致安全错误)。

var newWorker = function (funcObj) {
    // Build a worker from an anonymous function body
    var blobURL = URL.createObjectURL(new Blob(
        ['(', funcObj.toString(), ')()'],
        {type: 'application/javascript'}
     ));

    var worker = new Worker(blobURL);

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

    return worker;
}

下面是一个工作示例http://jsfiddle.net/ubershmekel/YYzvr/

根据您的用例,您可以使用类似

简化的界面,让CPU密集型代码运行在所有核心(node.js和web)

一个例子是

function blocking (exampleArgument) {
    // block thread
}

// turn blocking pure function into a worker task
const blockingAsync = task.wrap(blocking);

// run task on a autoscaling worker pool
blockingAsync('exampleArgumentValue').then(result => {
    // do something with result
});

您可以创建一个JavaScript文件,该文件可以感知其执行上下文,并且可以充当父脚本和工作脚本。让我们从这样一个文件的基本结构开始:

(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);

如您所见,脚本包含父进程和工作者进程的所有代码,检查它自己的实例是否是一个具有!document的工作者进程。有点笨拙的script_path计算用于精确计算相对于父页的脚本路径,因为提供给新Worker的路径是相对于父页的,而不是相对于脚本的。