没有单独的Javascript文件的网络工作者?


291

据我所知,网络工作者需要用一个单独的JavaScript文件编写,并按如下方式调用:

new Worker('longrunning.js')

我正在使用闭包编译器来合并和最小化我的所有JavaScript源代码,而我不必将我的工作人员放在单独的文件中进行分发。有什么办法可以做到这一点?

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

鉴于一流的功能对于JavaScript至关重要,为什么进行后台工作的标准方法必须从Web服务器加载整个其他JavaScript文件?


7
这是因为保持执行上下文纯粹是线程安全的,比一流的函数甚至更重要:-)
Pointy

1
我正在研究它(或者说是最小化问题):DynWorker。你可以这样做:var worker = new DynWorker(); worker.inject("foo", function(){...});...
费利克斯Saparelli


1
OP删除了“教学工作者接受功能而不是JavaScript源文件”的问题。答案在此处重新发布
Rob W

我开发了task.js使其更容易实现。大多数情况下,您只是在尝试卸载小型锁定任务。
乍得·史基拉

Answers:


225

http://www.html5rocks.com/zh-CN/tutorials/workers/basics/#toc-inlineworkers

如果您想即时创建工作脚本或创建独立页面而不需要创建单独的工作文件怎么办?使用Blob(),您可以通过将工作程序代码的URL句柄创建为字符串,将工作程序“内联”到与主逻辑相同的HTML文件中


BLOB内联工作程序的完整示例:

<!DOCTYPE html>
<script id="worker1" type="javascript/worker">
  // This script won't be parsed by JS engines because its type is javascript/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>


Google Chrome唯一的解决方案,似乎Firefox 10将支持它,我不知道其他浏览器
4esn0k 2011年

2
BlobBuiler 现在已弃用。请改用Blob。最新的Firefox / WebKit / Opera和IE10当前受支持,请参阅旧版浏览器的兼容性表
费利克斯Saparelli

3
IE10中可能支持Blob构造函数,但是您仍然无法通过它将javascript传递给Web Worker(甚至在IE11中也不是):connect.microsoft.com/IE/feedback/details/801810/…
jayarjo 2014年

1
@albanx-什么测试?已经有十亿个在线演示页面,这些页面显示线程多年来没有挂断浏览器。
vsync

2
@albanx-您是否愿意至少说出您使用哪个深奥的浏览器挂起?这个演示适合您吗?ie.microsoft.com/testdrive/Graphics/WorkerFountains/...
VSYNC

162

将Web Worker代码嵌入HTML的html5rocks解决方案相当糟糕。
一串转义的JavaScript字符串并不是更好,这不仅因为它使工作流程复杂(Closure编译器无法对字符串进行操作)。

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

我的首选方法:

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

支持是这三个表的交集:

但是,这对于SharedWorker无效,因为URL必须精确匹配,即使可选的“ name”参数匹配也是如此。对于SharedWorker,您需要一个单独的JavaScript文件。


2015年更新-ServiceWorker的奇异之处

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

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

这种方法的优点是:

  1. 还可以支持SharedWorkers。
  2. 选项卡可以在固定地址共享一个缓存的副本。Blob方法会为每个选项卡扩散随机的objectURL。

4
此解决方案的浏览器兼容性会是什么样?
Ben Dilts 2013年

您能否详细说明此解决方案,它如何工作?什么是worker1.js?它是一个单独的js文件吗?我正在尝试使用此功能,但无法使其正常工作。具体来说,我正在试图使其正常工作的SharedWorker
耶胡达·

如果您可以将其包装在一个有用的功能中!
毫米

@ Ben Dilts:浏览器兼容性看起来就像只是通过babel运行代码:babeljs.io/repl
Jack Giffin

该标准不保证Function.prototype.toString()以字符串形式返回函数主体。您可能应该在答案中添加警告。
RD

37

您可以创建一个知道其执行上下文的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提供的路径new Worker是相对于父页面而不是脚本的,因此有些笨拙的计算用于准确地计算相对于父页面的脚本的路径。


4
您的网站似乎已消失;您有新的网址吗?
BrianFreud 2012年

1
这是一个有趣的方法。FWIW,我通过检查“自身”(Web Worker全局对象)和“窗口”是否存在来检测Web Worker。
pwnall

我一直在研究PapaParse如何处理Web Workers,他们似乎采用了这种方法github.com/mholt/PapaParse
JP DeVries

我认为使用'typeof importScripts!== null'进行的测试可以判断脚本是否在工作程序范围内运行。
MeTTeO

1
我不明白脚本元素中的previousSibling是什么。有人可以向我解释吗?
Teemoh

28

使用该Blob方法,对于工人工厂如何处理:

var BuildWorker = function(foo){
   var str = foo.toString()
             .match(/^\s*function\s*\(\s*\)\s*\{(([\s\S](?!\}$))*[\s\S])/)[1];
   return  new Worker(window.URL.createObjectURL(
                      new Blob([str],{type:'text/javascript'})));
}

所以你可以这样使用它...

var myWorker = BuildWorker(function(){
   //first line of worker
   self.onmessage(){....};
   //last line of worker
});

编辑:

我只是进一步扩展了这个想法,以使其更易于进行跨线程通信:bridged-worker.js

编辑2:

上面的链接是我创建的要点。后来有人把它变成了实际的仓库


11

Web worker作为单独的程序在完全独立的上下文中运行。

这意味着代码无法以对象形式从一个上下文移动到另一个上下文,因为它们随后将能够通过属于另一上下文的闭包来引用对象。
这一点尤其重要,因为ECMAScript被设计为单线程语言,并且由于Web工作人员在单独的线程中进行操作,因此您将面临执行非线程安全操作的风险。

这再次意味着需要使用源形式的代码来初始化Web Worker。

WHATWG的规范说

如果结果绝对URL的来源与输入脚本的来源不同,则抛出SECURITY_ERR异常。

因此,脚本必须是具有与原始页面相同方案的外部文件:您不能从data:URL或javascript:URL加载脚本,并且https:页面无法使用带有http:URL的脚本来启动工作程序。

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


6

内联人员更好的阅读方式。

    var worker_fn = function(e) 
    {
        self.postMessage('msg from worker');            
    };

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

    var worker = new Worker(window.URL.createObjectURL(blob));
    worker.onmessage = function(e) 
    {
       alert(e.data);
    };
    worker.postMessage("start"); 

我所做的是,我创建了一个包含所有辅助代码的函数toString(),将其传递给函数,引出主体,然后将其放入Blob中。检查最后一个答案,我有一个例子
Fernando Carvajal '18

5

接受Adria的响应并将其放入可复制复制的函数中,该函数可与当前的Chrome和FF一起使用,但不适用于IE10(来自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/


5

最近回答(2018)

您可以使用Greenlet

将异步函数移到其自己的线程中。Workerize的简化的单功能版本。

例:

import greenlet from 'greenlet'

const getName = greenlet(async username => {
  const url = `https://api.github.com/users/${username}`
  const res = await fetch(url)
  const profile = await res.json()
  return profile.name
})

console.log(await getName('developit'))

3

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

task.js简化的界面,用于使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
});


1

您可以使用内联Webworkers在相同的javascript fie中使用web worker。

下面的文章将解决您的问题,以使他们轻松理解网络工作者及其局限性和网络工作者的调试。

掌握网络工作者


1

我认为更好的方法是使用Blob对象,您可以在下面看到一个简单的示例。

// create a Blob object with a worker code
var blob = new Blob(["onmessage = function(e) { postMessage('msg from worker'); }"]);

// Obtain a blob URL reference to our worker 'file'.
var blobURL = window.URL.createObjectURL(blob);

// create a Worker
var worker = new Worker(blobURL);
worker.onmessage = function(e) {
  console.log(e.data);
};
worker.postMessage("Send some Data"); 


1

在这里控制台:

var worker=new Worker(window.URL.createObjectURL(new Blob([function(){
  //Long-running work here
  postMessage('done');
}.toString().split('\n').slice(1,-1).join('\n')],{type:'text/javascript'})));

worker.addEventListener('message',function(event){
  console.log(event.data);
});

1

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

})();


1

因此,由于ES6中的模板文字,我认为我们现在对此还有另一个不错的选择。这使我们可以省去多余的worker函数(及其怪异的作用域),而只需将用于该worker的代码编写为多行文本,就像我们以前存储文本的情况一样,但是实际上不需要文档或DOM为此。示例:

const workerScript = `
self.addEventListener('message', function(e) {
  var data = e.data;
  console.log('worker recieved: ',data);
  self.postMessage('worker added! :'+ addOne(data.value));
  self.close();//kills the worker
}, false);
`;

是该方法其余部分的要点

请注意,我们可以通过将所需的任何其他函数依赖关系收集到工作器中,只需将它们收集到一个数组中,然后对它们中的每个对象运行.toString并将它们也缩减为字符串(只要它们是函数声明,就应该起作用),并且然后将其放在脚本字符串之前。这样,我们就不必导入可能已经捆绑到正在编写的代码范围中的脚本。

该特定版本的唯一实际缺点是,lints无法使服务工作程序代码(因为它只是一个字符串)变得很短,这对于“单独的工作程序函数方法”来说是一个优势。


1

这只是上述内容的补充-我有一个不错的模板,用于测试jsFiddle中的Web worker。而不是Blob,它使用jsFiddles ?jsapi:

function workerFN() {
  self.onmessage = function(e) {
    switch(e.data.name) {
      case "" : 
      break;
      default:
        console.error("Unknown message:", e.data.name);
    }
  }
}
// This is a trick to generate real worker script that is loaded from server
var url = "/echo/js/?js="+encodeURIComponent("("+workerFN.toString()+")()");
var worker = new Worker(url);
worker.addEventListener("message", function(e) {
  switch(e.data.name) {
    case "" : 
    break;
    default:
      console.error("Unknown message:", e.data.name);
  }
})

可以使用普通的Web工作程序共享工作程序模板。


1

我发现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>


1

一个简单的承诺版本,Function#callAsWorker它接受thisArg和参数(就像一样call),并返回一个promise:

Function.prototype.callAsWorker = function (...args) {
    return new Promise( (resolve, reject) => {
        const code = `self.onmessage = e => self.postMessage((${this.toString()}).call(...e.data));`,
            blob = new Blob([code], { type: "text/javascript" }),
            worker = new Worker(window.URL.createObjectURL(blob));
        worker.onmessage = e => (resolve(e.data), worker.terminate());
        worker.onerror = e => (reject(e.message), worker.terminate());
        worker.postMessage(args);
    });
}

// Demo
function add(...nums) {
    return nums.reduce( (a,b) => a+b );
}
// Let the worker execute the above function, with the specified arguments
add.callAsWorker(null, 1, 2, 3).then(function (result) {
    console.log('result: ', result);
});


您应该添加close()方法来关闭网络工作者的生命周期。developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/...
沙哈尔ドーン列维

@ShaharドーンLevi,该close功能已弃用。但是,工人可以被解雇。我现在添加了。
特里科特

0

我使用这样的代码,您可以将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;
}


看看我的回答,我只是这样做了,但是我写了整个类来抽象如何传递回调
Fernando Carvajal

0

是的,有可能,我使用Blob文件并传递了回调

我将向您展示我编写的类的功能以及它如何在后台管理回调的执行。

首先GenericWebWorker,您要使用要传递给回调的任何数据实例化,这些数据将在中执行Web Worker,其中包括您要使用的函数,在这种情况下为数字,日期和称为blocker

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

此拦截器功能将无限执行n毫秒

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


0

您可以将worker.js文件的内容放在反引号内(允许使用多行字符串常量),然后从这样的Blob创建worker:

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" })));

如果出于某种原因您不想为工作人员使用单独的脚本标签,这将非常方便。


0

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

-1

一线在工人中运行功能:

const FunctionalWorker = fn => new Worker(window.URL.createObjectURL(new Blob(["(" + workerCode.toString() + ")()"], {type: "text/javascript"})));

用法示例:

let fn = FunctionalWorker(() => {
    self.postMessage("hi");
});
fn.onmessage = msg => {
    console.log(msg);
};
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.