Pregunta

Por lo que puedo decir, los trabajadores web deben ser escritos en un archivo JavaScript separado y llamados así:

new Worker('longrunning.js')

Estoy usando el compilador de cierre para combinar y minificar todo mi código fuente de JavaScript, y prefiero no tener que tener a mis trabajadores en archivos separados para su distribución. Hay alguna manera de hacer esto?

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

Dado que las funciones de primera clase son tan cruciales para JavaScript, ¿por qué la forma estándar de hacer el trabajo de fondo tiene que cargar otro archivo de JavaScript desde el servidor web?

¿Fue útil?

Solución

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

¿Qué pasa si desea crear su script de trabajadores en la marcha o crear una página autónoma sin tener que crear archivos de trabajadores separados? Con blob (), puede "en línea" a su trabajador en el mismo archivo HTML que su lógica principal creando un mango de URL para el código del trabajador como una cadena


Ejemplo completo del trabajador en línea 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>

Otros consejos

La solución HTML5Rocks de incrustar el código de trabajadores web en HTML es bastante horrible.
Y una gota de JavaScript como una cuerda escapada no es mejor, sobre todo porque complica el flujo de trabajo (el compilador de cierre no puede funcionar en cadenas).

Personalmente me gustan mucho los métodos de tostración, pero @Dan-Man Ese regex!

Mi enfoque preferido:

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

El soporte es la intersección de estas tres tablas:

Esto no funcionará para un Trabajador compartido Sin embargo, debido a que la URL debe ser una coincidencia exacta, incluso si el parámetro opcional 'Nombre' coincide. Para un trabajador compartido, necesitará un archivo JavaScript separado.


Actualización de 2015: llega la singularidad de ServiceWorker

Ahora hay una forma aún más poderosa de resolver este problema. Nuevamente, almacene el código del trabajador en función (en lugar de una cadena estática) y convierta usando .ToString (), luego inserte el código en Cachestorage bajo una URL estática de su elección.

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

Hay dos posibles caídas. Objecturl como arriba, o más sin problemas, ponga un real Archivo JavaScript AT /MY_Workers/Worker1.js

Las ventajas de este enfoque son:

  1. Los trabajadores compartidos también pueden ser apoyados.
  2. Las pestañas pueden compartir una sola copia en caché en una dirección fija. El enfoque BLOB prolifera objetos aleatorios para cada pestaña.

Puede crear un solo archivo JavaScript que conozca su contexto de ejecución y pueda actuar como un script principal como un trabajador. Comencemos con una estructura básica para un archivo 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('<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);

Como puede ver, el script contiene todo el código para el punto de vista de los padres y del trabajador, verificando si su propia instancia individual es un trabajador con !document. El algo difícil de manejar script_path El cálculo se utiliza para calcular con precisión la ruta del script en relación con la página principal, como la ruta suministrada a new Worker es relativo a la página principal, no con el script.

Utilizando el Blob Método, ¿qué tal esto para una fábrica de trabajadores:

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

Para que puedas usarlo así ...

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

EDITAR:

Acabo de extender esta idea aún más para que sea más fácil hacer una comunicación de hilo cruzado: puente-worker.js.

Editar 2:

El enlace anterior es a una esencia que creé. Alguien más luego lo convirtió en un repositorio real.

Los trabajadores web operan en contextos completamente separados como programa individual.

Esto significa que el código no se puede mover de un contexto a otro en forma de objeto, ya que luego podrían hacer referencia a objetos a través de cierres pertenecientes al otro contexto.
Esto es especialmente crucial ya que EcMascript está diseñado para ser un lenguaje enheciente, y dado que los trabajadores web operan en hilos separados, entonces tendrá el riesgo de realizar operaciones no seguras.

Esto nuevamente significa que los trabajadores web deben inicializarse con código en forma de origen.

La especificación de Whatwg dice

Si el origen de la URL absoluta resultante no es el mismo que el origen del script de entrada, entonces arroje una excepción Security_ERR.

Por lo tanto, los scripts deben ser archivos externos con el mismo esquema que la página original: no puede cargar un script de un datos: URL o JavaScript: URL, y una página https: no podría iniciar trabajadores usando scripts con http: urls.

Pero desafortunadamente realmente no explica por qué uno no podría haber permitido pasar una cadena con el código fuente al constructor.

Una forma mejor para leer para un trabajador en línea.

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

Respuesta reciente (2018)

Puedes usar Vireillo:

Mueva una función de async a su propio hilo. Una versión simplificada de una sola función de Estamparse.

Ejemplo:

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

Tomar la respuesta de Adria y ponerla en una función de copia-pasta que funciona con Chrome y FF actuales pero no IE10 (el trabajador de Blob causa un error de seguridad).

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

Y aquí hay un ejemplo de funcionamiento http://jsfiddle.net/ubershmekel/yyzvr/

Eche un vistazo al complemento VKThread. Con el complemento HTIS puede tomar cualquier función en su código principal y ejecutarla en un hilo (trabajador web). Por lo tanto, no necesita crear un "archivo de trabajo web" especial.

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

-Vadim

Dependiendo de su caso de uso, puede usar algo como

tarea.js Interfaz simplificada para obtener el código intensivo de la CPU se ejecuta en todos los núcleos (Node.js y Web)

Un ejemplo sería

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

Puede usar trabajadores web en el mismo JavaScript FIE usando WebWorkers en línea.

El siguiente artículo se dirigirá a usted para comprender fácilmente los trabajadores web y sus limitaciones y depuración de los trabajadores web.

Mastering en trabajadores web

Creo que la mejor manera de hacer esto es usar un objeto Blob, a continuación puede ver un ejemplo simple.

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

Intenta usar 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

aquí consola:

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

})();

Usa mi pequeño complemento 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);

Así que creo que tenemos otra opción genial para esto ahora, gracias a las plantillas literales en ES6. Eso nos permite prescindir de la función adicional del trabajador (y su alcance extraño) y simplemente escribir el código destinado al trabajador como texto multiline, al igual que el caso en el que estábamos usando para almacenar texto, pero sin necesidad de un documento o DOM Para hacer eso. Ejemplo:

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

Aquí está un esencial del resto de ese enfoque.

Tenga en cuenta que podemos atraer cualquier dependencia de funciones adicionales que queramos en el trabajador simplemente recolectándolas en una matriz y ejecutando. Luego, solo prefiriéndolo a la cadena de script. De esa manera, no tenemos que importaciones que ya nos hayamos envuelto en el alcance del código que estamos escribiendo.

El único inconveniente real de esta versión en particular es que las puntas no podrán vincular el código del trabajador de servicio (ya que es solo una cadena), lo cual es una ventaja para el "enfoque de función del trabajador separado".

Esto es solo una adición de lo anterior: tengo buenas plantillas para probar trabajadores web en JSFIDDLE. En lugar de blob 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);
  }
})

Normal trabajador web y trabajador compartido Las plantillas están disponibles.

Descubrí que CodePen actualmente no es sintaxis en línea <script> Etiquetas que no son type="text/javascript" (o que no tienen atributo de tipo).

Así que ideé una solución similar pero ligeramente diferente usando Bloques etiquetados con break, que es la única forma en que puedes rescatar de un <script> Etiqueta sin crear una función de envoltura (que es innecesaria).

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

Una versión simple prometida, Function#callAsWorker, que requiere un thantarg y argumentos (al igual que call), y devuelve una promesa:

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

Utilizo un código como este, puede definir su OnMessage como una función que no sea texto sin formato, para que el editor pueda resaltar su código y 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;
}

Sí, es posible, lo hice usando archivos blob y aprobando una devolución de llamada

Te mostraré lo que hace una clase que escribí y cómo administra la ejecución de devoluciones de llamada en segundo plano.

Primero instancías el GenericWebWorker con los datos que desee pasar a la devolución de llamada que se ejecutará en el Web Worker, que incluye funciones que desea usar, en este caso un número, una fecha y una función llamada blocker

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

Esta función de bloqueador ejecutará un infinito, mientras que para n milisegundos

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

Y luego lo usas así

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

Ahora, es hora de ver la magia en el siguiente ejemplo

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

Puede colocar el contenido de su archivo Worker.js dentro de Backticks (que permite una constante de cadena multilínea) y crear el trabajador desde una blob como esta:

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

Esto es útil si por alguna razón no desea tener etiquetas de script separadas para el trabajador.

Otra solución es solo para envolver al trabajador en una función, luego crear un blob que invoque la función de así decirlo:

     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.
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top