首页 文章

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

提问于
浏览
249

据我所知,网络工作者需要在一个单独的JavaScript文件中编写,并像这样调用:

new Worker('longrunning.js')

我正在使用闭包编译器来组合和缩小我的所有JavaScript源代码,而我宁愿不必将我的worker放在单独的文件中进行分发 . 有办法做到这一点吗?

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

鉴于第一类函数对JavaScript至关重要,为什么执行后台工作的标准方法必须从服务器加载整个'其他JavaScript文件?

23 回答

  • 135

    http://www.html5rocks.com/en/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>
    
  • 4

    看看vkThread插件 . 使用htis插件,您可以在主代码中执行任何功能并在线程(Web worker)中执行它 . 因此,您无需创建特殊的“Web-worker文件” .

    http://www.eslinstructor.net/vkthread/

    --Vadim

  • 2

    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

    Web工作者在完全独立的环境中作为单独的程序运行 .

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

    这再次意味着需要使用源代码中的代码初始化Web worker .

    来自WHATWG的规范说

    如果生成的绝对URL的原点与条目脚本的原点不同,则抛出SECURITY_ERR异常 . 因此,脚本必须是与原始页面具有相同方案的外部文件:您无法从数据加载脚本:URL或javascript:URL,并且https:页面无法使用带有http:URL的脚本启动工作程序 .

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

  • 11

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

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

    所以我设计了一个类似但略有不同的解决方案,使用labeled blocksbreak ,这是你可以从 <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

    所以我认为我们现在有另一个很酷的选择,这要归功于ES6中的模板文字 . 这允许我们省去额外的工作者函数(及其奇怪的范围),并且只是将作为工作者的代码编写为多行文本,就像我们用来存储文本的情况一样,但实际上不需要文档或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);
    `;
    

    这是gist of the rest of that approach .

    请注意,我们可以将所需的任何额外函数依赖项引入到worker中,只需将它们收集到一个数组中并在每个函数上运行.toString以将它们缩减为字符串(只要它们是函数声明就应该工作)和然后只是将其添加到脚本字符串中 . 这样我们就不必将我们可能已经捆绑的importScripts捆绑到我们正在编写的代码范围内 .

    这个特定版本唯一真正的缺点是,linters将无法lint服务工作者代码(因为它只是一个字符串),这对于“单独的工作者函数方法”是一个优势 .

  • 1

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

    EDIT:

    我刚刚进一步扩展了这个想法,以便更容易进行跨线程通信:bridged-worker.js .

    EDIT 2:

    以上链接是我创建的要点 . 其他人后来把它变成了actual repo .

  • 1

    最近回答(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'))
    
  • 23

    根据您的使用情况,您可以使用类似的东西

    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

    您可以将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" })));
    

    如果由于某种原因您不希望为工作者提供单独的脚本标记,这将非常方便 .

  • 185

    使用我的小插件https://github.com/zevero/worker-create

    var worker_url = Worker.createURL(function(e){
      self.postMessage('Example post from Worker'); //your code here
    });
    var worker = new Worker(worker_url);
    
  • 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

    您可以创建一个知道其执行上下文的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);
    

    如您所见,该脚本包含父's and the worker'视点的所有代码,检查其自己的单个实例是否是具有 !document 的工作程序 . 稍微笨拙的 script_path 计算用于准确计算脚本相对于父页面的路径,因为提供给 new Worker 的路径是相对于父页面而不是脚本 .

  • 35

    采取Adria的回应并将其置于可复制的功能中,该功能适用于当前的Chrome和FF但不适用于IE10(来自blob的工作人员会导致security error) .

    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/

  • 2

    这只是上面的一个补充 - 我有一个很好的模板来测试jsFiddle中的web worker . 而不是Blob它使用jsFiddles ?js api:

    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 workershared worker模板可用 .

  • 0

    控制台:

    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

    一个简单的promisified版本 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.onerror = e => reject(e.message);
            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);
    });
    
  • 1

    您可以使用内联Web工作者在同一个javascript中使用Web worker .

    下面的文章将向您介绍如何轻松了解Web工作者及其对Web工作者的限制和调试 .

    Mastering in webworkers

  • 4

    内联工作者更好的阅读方式..

    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");
    
  • 0

    尝试使用jThread . https://github.com/cheprasov/jThread

    // You can use simple calling like this
    jThread(
        function(arr){
            //... some code for Worker
            return arr;
        }
        ,function(arr){
            //... done code
        }
    )( [1,2,3,4,5,6,7] ); // some params
    
  • 6

    Yes, it is possible, I did it using Blob files and passing a callback

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

    首先使用 Web Worker 中正在执行的任何数据实例化 GenericWebWorker ,其中包括您要使用的函数,在本例中为数字,日期和函数 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

    将HTML工作者代码嵌入HTML的html5rocks解决方案相当糟糕 .
    并且一堆逃脱的JavaScript-as-a-string并不是更好,尤其是因为它使工作流复杂化(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 ,因为即使可选的'name'参数匹配,URL也必须完全匹配 . 对于SharedWorker,您需要一个单独的JavaScript文件 .


    2015 update - ServiceWorker奇点到来

    现在有一个更强大的方法来解决这个问题 . 再次,将worker代码存储为函数(而不是静态字符串)并使用.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如上所述,或者更无缝地将 real JavaScript文件放在/my_workers/worker1.js上

    这种方法的优点是:

    • 也可以支持SharedWorkers .

    • 选项卡可以在固定地址共享单个缓存副本 . blob方法为每个选项卡增加随机objectURL .

相关问题