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

new Worker('longrunning.js')

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

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

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


当前回答

近期答案(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'))

其他回答

有一些答案,但这里是另一个内联版本。

注意:“self”参数纯粹是为了检测目的而做的修饰,实际的工作代码从第一个大括号开始,self是正常的

inlineWorker ( "hello world",// initial message to send to worker function(self){ // inline worker code. self.onmessage = function (e) { self.postMessage("thinking..."); for (var i=0;i<100000000;i++) { var r = Math.random(); } self.postMessage(e.data.toUpperCase()); } },function(e){ // optional message handler document.getElementById("log").innerHTML= "from worker:"+e.data; }); function inlineWorker (msg,fn,onMsg) { var w=window, U=!!w.webkitURL?w.webkitURL:w.URL, src=fn.toString(), s=src.indexOf('{'), e=src.lastIndexOf('}'), worker = new Worker(U.createObjectURL( new Blob([ src.substring(s+1,e-1) ], { type: "text/javascript" }) )); if (typeof onMsg==="function") { worker.addEventListener("message",onMsg); } if (msg) { worker.postMessage(msg); } return worker; } <div id="log"></div>

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

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

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

WHATWG的规格说明说

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

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

您可以创建一个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的路径是相对于父页的,而不是相对于脚本的。

近期答案(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'))

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