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

new Worker('longrunning.js')

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

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

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


当前回答

这是一个有点离题的答案,但是……您可能不需要使用网络工作者来处理浏览器上的长时间工作。

让我们假设你想要运行几次繁重的计算(就像你对数组做的那样):

const heavyFct = () => {let i = 0; while(i<1e8) {++i}}

for (let i = 0; i < 100; ++i) {
  heavyFct();
}

这将冻结您的浏览器。

为了避免这种情况,我们可以这样依赖setTimeout:

const desync = (i = 0) => {
  if (i >= 100) {return}
  heavyFct();
  setTimeout(() => desync(i + 1), 0);
}
desync();

现在,您可以在不冻结计算机的情况下运行繁重的计算

其他回答

是的,这是可能的,我做到了使用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

我使用这样的代码,你可以将onmessage定义为一个函数而不是纯文本,这样编辑器就可以突出显示你的代码和jshint工作。

const worker = createWorker(); createWorker() { const scriptContent = getWorkerScript(); const blob = new Blob([ scriptContent, ], { type: "text/javascipt" }); const worker = new Worker(window.URL.createObjectURL(blob)); return worker; } getWorkerScript() { const script = { onmessage: function (e) { console.log(e); let result = "Hello " + e.data postMessage(result); } }; let content = ""; for (let prop in script){ content += `${prop}=${script[prop].toString()}`; } return content; }

网络工作者在完全独立的上下文中操作,就像单个程序一样。

这意味着代码不能以对象的形式从一个上下文中移动到另一个上下文中,因为它们可以通过属于另一个上下文中的闭包引用对象。 这一点尤其重要,因为ECMAScript被设计成一种单线程语言,并且由于web worker在单独的线程中操作,那么您将有执行非线程安全操作的风险。

这再次意味着web工作者需要用源代码形式的代码进行初始化。

WHATWG的规格说明说

如果原点的结果 绝对URL是不一样的 输入脚本的起源,然后抛出 SECURITY_ERR异常。 因此,脚本必须是外部文件 和原来一样的方案 页面:您无法从 data: URL或javascript: URL,和一个 页面无法启动工作 使用http: url脚本。

但不幸的是,它并没有真正解释为什么不允许将带有源代码的字符串传递给构造函数。

将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/

另一个解决方案是将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.