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

new Worker('longrunning.js')

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

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

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


当前回答

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

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

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

WHATWG的规格说明说

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

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

其他回答

对于Node.js实现,可以使用以下对Trincot答案的改编。再次注意,Function.prototype.callAsWorker()接受一个thisArg和参数,就像Function.prototype.call()一样,并返回一个承诺。

const { Worker } = require ( 'worker_threads' );

Function.prototype.callAsWorker = function ( ...args ) {
    return new Promise( ( resolve, reject ) => {

        const code = `
            const { parentPort, workerData } = require ( 'worker_threads' );
            parentPort.postMessage( ( ${this.toString()} ).call( ...workerData ) )
        `;
        const worker = new Worker( code, { eval: true, workerData: args } );
            
        worker.on('message', ( msg ) => { resolve( msg ), worker.terminate() } );
        worker.on('error', ( err ) => { reject( err ), worker.terminate() } );
        worker.on('exit', ( code ) => {
            if ( code !== 0 ) {
                reject( new Error( `Worker stopped with exit code ${code}.` ) );
            }
        });

    });
}

// Demo
function add( ...nums ) {
    return nums.reduce( ( a, b ) => a + b );
}

// Let the worker execute the above function, with the specified arguments
let result = await add.callAsWorker( null, 1, 2, 3 );
console.log( 'result: ', result );

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

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

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

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

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

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

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

如果您希望动态地创建工作人员脚本,或者创建一个自包含的页面,而不必创建单独的工作人员文件,该怎么办?使用Blob(),您可以通过将工作代码的URL句柄创建为字符串,将工作代码“内联”到与主逻辑相同的HTML文件中


BLOB inline worker的完整示例:

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