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

new Worker('longrunning.js')

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

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

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


当前回答

你可以把worker.js文件的内容放在反勾号(允许多行字符串常量)中,然后像这样创建一个blob:

var workerScript = `
    self.onmessage = function(e) {
        self.postMessage('message from worker');
    };
    // rest of worker code goes here
`;

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

如果出于某种原因,您不想为worker使用单独的脚本标记,那么这是非常方便的。

其他回答

是的,这是可能的,我做到了使用Blob文件和传递回调

我将向您展示我编写的一个类是做什么的,以及它如何在后台管理回调的执行。

首先,你要实例化GenericWebWorker,使用你想要传递给回调的任何数据,这些数据将在WebWorker中执行,包括你想要使用的函数,在这种情况下,一个数字,一个日期和一个叫做blocker的函数

var worker = new GenericWebWorker(100, new Date(), blocker)

这个阻塞器函数将在n毫秒内执行一个无限while

function blocker (ms) {
    var now = new Date().getTime();
    while(true) {
        if (new Date().getTime() > now +ms)
            return;
    }   
}

然后像这样用

worker.exec((num, date, fnBlocker) => {
    /*Everithing here does not block the main thread
      and this callback has access to the number, date and the blocker */
    fnBlocker(10000) //All of this run in backgrownd
    return num*10

}).then(d => console.log(d)) //Print 1000

现在,是时候看看下面例子中的神奇之处了

/*https://github.com/fercarvo/GenericWebWorker*/ class GenericWebWorker { constructor(...ags) { this.args = ags.map(a => (typeof a == 'function') ? {type:'fn', fn:a.toString()} : a) } async exec(cb) { var wk_string = this.worker.toString(); wk_string = wk_string.substring(wk_string.indexOf('{') + 1, wk_string.lastIndexOf('}')); var wk_link = window.URL.createObjectURL( new Blob([ wk_string ]) ); var wk = new Worker(wk_link); wk.postMessage({ callback: cb.toString(), args: this.args }); var resultado = await new Promise((next, error) => { wk.onmessage = e => (e.data && e.data.error) ? error(e.data.error) : next(e.data); wk.onerror = e => error(e.message); }) wk.terminate(); window.URL.revokeObjectURL(wk_link); return resultado } async parallel(arr, cb) { var res = [...arr].map(it => new GenericWebWorker(it, ...this.args).exec(cb)) var all = await Promise.all(res) return all } worker() { onmessage = async function (e) { try { var cb = new Function(`return ${e.data.callback}`)(); var args = e.data.args.map(p => (p.type == 'fn') ? new Function(`return ${p.fn}`)() : p); try { var result = await cb.apply(this, args); //If it is a promise or async function return postMessage(result) } catch (e) { throw new Error(`CallbackError: ${e}`) } } catch (e) { postMessage({error: e.message}) } } } } function blocker (ms) { var now = new Date().getTime(); while(true) { if (new Date().getTime() > now +ms) return; } } setInterval(()=> console.log("Not blocked " + Math.random()), 1000) console.log("\n\nstarting blocking code in Worker\n\n") var worker = new GenericWebWorker(100, new Date(), blocker) worker.exec((num, date, fnBlocker) => { fnBlocker(7000) //All of this run in backgrownd return num*10 }) .then(d => console.log(`\n\nEnd of blocking code: result ${d}\n\n`)) //Print 1000

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

简化的界面,让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
});

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。

我发现CodePen目前不语法高亮内联<script>标签,不是type="text/javascript"(或没有类型属性)。

因此,我设计了一个类似但略有不同的解决方案,使用带break的标记块,这是摆脱<script>标记而不创建包装器函数(这是不必要的)的唯一方法。

<!DOCTYPE html> <script id="worker1"> worker: { // Labeled block wrapper if (typeof window === 'object') break worker; // Bail if we're not a 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>

https://developer.mozilla.org/es/docs/Web/Guide/Performance/Using_web_workers

    // Syntax: asyncEval(code[, listener])

var asyncEval = (function () {

  var aListeners = [], oParser = new Worker("data:text/javascript;charset=US-ASCII,onmessage%20%3D%20function%20%28oEvent%29%20%7B%0A%09postMessage%28%7B%0A%09%09%22id%22%3A%20oEvent.data.id%2C%0A%09%09%22evaluated%22%3A%20eval%28oEvent.data.code%29%0A%09%7D%29%3B%0A%7D");

  oParser.onmessage = function (oEvent) {
    if (aListeners[oEvent.data.id]) { aListeners[oEvent.data.id](oEvent.data.evaluated); }
    delete aListeners[oEvent.data.id];
  };


  return function (sCode, fListener) {
    aListeners.push(fListener || null);
    oParser.postMessage({
      "id": aListeners.length - 1,
      "code": sCode
    });
  };

})();