Trabalhadores da Web sem um arquivo JavaScript separado?

Tanto quanto eu posso dizer, os web workers precisam ser escritos em um arquivo JavaScript separado, e chamados assim:

new Worker('longrunning.js') 

Eu estou usando o compilador de fechamento para combinar e minify todo o meu código-fonte JavaScript, e eu prefiro não ter meus trabalhadores em arquivos separados para distribuição. Existe alguma maneira de fazer isso?

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

Como as funções de primeira class são tão importantes para o JavaScript, por que a maneira padrão de fazer o trabalho em segundo plano precisa carregar um arquivo JavaScript completo do servidor?

   

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

E se você quiser criar seu script de trabalho em tempo real ou criar uma página independente sem precisar criar arquivos de trabalho separados? Com Blob (), você pode “incorporar” seu worker no mesmo arquivo HTML de sua lógica principal, criando um identificador de URL para o código do worker como uma string

Exemplo completo de trabalhador em linha BLOB:

 < !DOCTYPE html>   

A solução html5rocks de incorporar o código do web worker em HTML é bastante horrível.
E uma gota de escape de JavaScript como uma string não é melhor, até porque complica o stream de trabalho (o compilador do Closure não pode operar em strings).

Pessoalmente eu realmente gosto dos methods toString, mas @ dan-man que regex!

Minha abordagem preferida:

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

Suporte é a interseção dessas três tabelas:

Isso não funcionará para um SharedWorker , pois o URL deve ser uma correspondência exata, mesmo que o parâmetro opcional ‘name’ corresponda. Para um SharedWorker, você precisará de um arquivo JavaScript separado.


Atualização de 2015 – A singularidade ServiceWorker chega

Agora há uma maneira ainda mais poderosa de resolver esse problema. Novamente, armazene o código do worker como uma function (em vez de uma string estática) e converta usando .toString (), depois insira o código em CacheStorage sob uma URL estática de sua escolha.

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

Existem dois possíveis atrasos. ObjectURL como acima, ou mais perfeitamente, coloque um arquivo JavaScript real em /my_workers/worker1.js

As vantagens desta abordagem são:

  1. SharedWorkers também podem ser suportados.
  2. Guias podem compartilhar uma única cópia em cache em um endereço fixo. A abordagem de blob prolifera objectURLs randoms para cada guia.

Você pode criar um único arquivo JavaScript que esteja ciente de seu contexto de execução e possa atuar como um script pai e um trabalhador. Vamos começar com uma estrutura básica para um arquivo como este:

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

Como você pode ver, o script contém todo o código para o ponto de vista do pai e do trabalhador, verificando se sua própria instância individual é um trabalhador com !document . O cálculo um pouco complicado do script_path é usado para calcular com precisão o caminho do script em relação à página pai, pois o caminho fornecido ao new Worker é relativo à página pai, não ao script.

Usando o método Blob , que tal isso para uma fábrica de trabalhadores:

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

Então você poderia usá-lo assim …

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

EDITAR:

Acabei de estender ainda mais essa ideia para facilitar a comunicação entre threads: bridged-worker.js .

EDIT 2:

O link acima é para uma essência que eu criei. Alguém mais tarde transformou-o em um repo real .

Os trabalhadores da Web operam em contextos totalmente separados como programas individuais.

Isso significa que o código não pode ser movido de um contexto para outro na forma de object, pois eles seriam capazes de referenciar objects através de closures pertencentes ao outro contexto.
Isso é especialmente crucial, pois o ECMAScript foi projetado para ser um único idioma encadeado e, como os operadores da Web operam em encadeamentos separados, você teria o risco de executar operações não seguras.

Novamente, isso significa que os funcionários da Web precisam ser inicializados com código no formato de origem.

A especificação do WHATWG diz

Se a origem da URL absoluta resultante não for igual à origem do script de input, ative uma exceção SECURITY_ERR.

Assim, os scripts devem ser arquivos externos com o mesmo esquema da página original: você não pode carregar um script de um dado: URL ou javascript: URL e uma página https: não pôde iniciar trabalhadores usando scripts com URLs http:.

mas infelizmente isso não explica realmente porque alguém não poderia ter permitido passar uma string com código-fonte para o construtor.

um melhor para ler o caminho para um trabalhador em linha ..

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

Tomando a resposta de Adria e colocando-a em uma function de copiar-pastável que funciona com o atual Chrome e FF, mas não IE10 (trabalhador do blob causa um erro de segurança ).

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

E aqui está um exemplo de trabalho http://jsfiddle.net/ubershmekel/YYzvr/

Resposta recente (2018)

Você pode usar o Greenlet :

Mova uma function assíncrona para o seu próprio segmento. Uma versão simplificada de function única do Workerize .

Exemplo:

 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')) 

Dê uma olhada no plugin vkThread. Com o plugin htis você pode ter qualquer function no seu código principal e executá-lo em um thread (web worker). Portanto, você não precisa criar um “arquivo de trabalhador da Web” especial.

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

– Vadim

Dependendo do seu caso de uso, você pode usar algo como

task.js Interface simplificada para obter código intensivo da CPU para ser executado em todos os núcleos (node.js e web)

Um exemplo seria

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

Você pode usar os funcionários da Web no mesmo javascript fie usando webworkers em linha.

O artigo abaixo irá abordar você para entender facilmente os webworkers e suas limitações e debugging de webworkers.

Domínio em webworkers

Eu acho que a melhor maneira de fazer isso é usando um object Blob, abaixo você pode ver um exemplo simples.

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

Tente usar o 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 

aqui console:

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

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

Use meu pequeno plugin 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); 

Então, acho que temos outra opção legal para isso agora, graças aos modelos literais no ES6. Isso nos permite dispensar a function de trabalhador extra (e seu escopo estranho) e apenas escrever o código que é destinado ao trabalhador como texto de múltiplas linhas, muito parecido com o caso em que estávamos usando para armazenar texto, mas sem precisar de um documento ou DOM para fazer isso. Exemplo:

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

Aqui está uma essência do resto dessa abordagem .

Note que podemos extrair quaisquer dependencies extras de function para o worker apenas coletando-as em uma matriz e executando .toString em cada uma delas para reduzi-las em strings também (deve funcionar desde que sejam declarações de function) e em seguida, basta colocar isso na seqüência de script. Dessa forma, não precisamos importar scripts que já tenhamos incluído no escopo do código que estamos escrevendo.

A única desvantagem real dessa versão em particular é que os linters não conseguirão liberar o código do service worker (já que é apenas uma string), o que é uma vantagem para a “abordagem da function de trabalho separada”.

Este é apenas um complemento para acima – eu tenho um bom modelo para testar web workers no jsFiddle. Em vez de usar o Blob, ele usa 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); } }) 

Modelos normais de trabalhador da web e de trabalhador compartilhado estão disponíveis.

Eu descobri que CodePen atualmente não syntax-realce inline tags que não são type="text/javascript" (ou que não têm atributo de tipo).

Então eu criei uma solução semelhante, mas ligeiramente diferente, usando blocos rotulados com break , que é a única maneira de liberar uma tag sem criar uma function wrapper (que é desnecessária).

 < !DOCTYPE html>   

Uma versão promisificada simples, Function#callAsWorker , que recebe um thisArg e argumentos (assim como o call ) e retorna uma 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); }); 

Eu uso código como esse, você pode definir sua mensagem como uma function diferente de texto simples, para que o editor possa destacar seu código e o jshint funciona.

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

Sim, é possível, eu fiz isso usando arquivos Blob e passando um retorno de chamada

Mostrarei a você o que uma class que escrevi faz e como ela gerencia a execução de retornos de chamada em segundo plano.

Primeiro você instancia o GenericWebWorker com todos os dados que deseja passar para o retorno de chamada que estará sendo executado no Web Worker , que inclui as funções que você deseja usar, neste caso um número, uma data e uma function chamada blocker

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

Esta function de bloqueador executará um infinito enquanto por milissegundos

 function blocker (ms) { var now = new Date().getTime(); while(true) { if (new Date().getTime() > now +ms) return; } } 

e então você usa assim

 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 

Agora, hora de ver a mágica no exemplo abaixo

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

Você pode colocar o conteúdo do seu arquivo worker.js dentro dos backticks (que permite uma constante de string de múltiplas linhas) e criar o worker a partir de um blob assim:

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

Isso é útil se, por qualquer motivo, você não quiser ter tags de script separadas para o trabalhador.