Domanda

Per quanto ne so, i lavoratori web devono essere scritti in un file javascript separato e chiamato come questo:

new Worker('longrunning.js')

Sto usando il compilatore di chiusura per combinare e minimizzare tutto il mio codice sorgente JavaScript e preferirei non dover avere i miei lavoratori in file separati per la distribuzione. C'è un modo per farlo?

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

Dato che le funzioni di prima classe sono così cruciali per JavaScript, perché il modo standard di fare il lavoro di fondo deve caricare un intero altro file JavaScript dal server Web?

È stato utile?

Soluzione

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

Cosa succede se si desidera creare il tuo script di lavoro al volo o creare una pagina autonoma senza dover creare file di lavoro separati? Con Blob (), puoi "in linea" il tuo lavoratore nello stesso file HTML della tua logica principale creando un impugnatura URL al codice del lavoratore come stringa


Esempio completo del lavoratore in linea 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>

Altri suggerimenti

La soluzione HTML5Rocks di incorporare il codice del lavoro in HTML è abbastanza orribile.
E una chiazza di javascript-as-string sfuggito non è migliore, non da ultimo perché complica il flusso di lavoro (il compilatore di chiusura non può operare su stringhe).

Personalmente mi piacciono molto i metodi di ToString, ma @Dan-Man Quel regex!

Il mio approccio preferito:

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

Il supporto è l'intersezione di queste tre tabelle:

Questo non funzionerà per un Sharedworker Tuttavia, poiché l'URL deve essere una corrispondenza esatta, anche se il parametro "nome" opzionale corrisponde. Per un sharedworker, avrai bisogno di un file JavaScript separato.


Aggiornamento 2015 - Arriva la singolarità del servizio

Ora c'è un modo ancora più potente di risolvere questo problema. Ancora una volta, memorizzare il codice del lavoratore come una funzione (piuttosto che una stringa statica) e convertire usando .ToString (), quindi inserire il codice in CacheStorage sotto un URL statico di tua scelta.

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

Ci sono due possibili back-back. Objecturl come sopra, o più senza soluzione di continuità, metti a vero File javascript all'indirizzo /my_workers/worker1.js

I vantaggi di questo approccio sono:

  1. I lavoratori shareding possono anche essere supportati.
  2. Le schede possono condividere una singola copia memorizzata nella cache a un indirizzo fisso. L'approccio BLOB prolifera gli oggetti casuali per ogni scheda.

È possibile creare un singolo file JavaScript che sia a conoscenza del suo contesto di esecuzione e può agire sia come script genitore che come lavoratore. Iniziamo con una struttura di base per un file come questo:

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

Come puoi vedere, lo script contiene tutto il codice sia per i genitori che per il punto di vista del lavoratore, controllando se la propria istanza individuale è un lavoratore con !document. Il un po 'ingombrante script_path Il calcolo viene utilizzato per calcolare accuratamente il percorso dello script rispetto alla pagina principale, come percorso fornito new Worker è relativo alla pagina principale, non allo script.

Usando il Blob Metodo, che ne dici di questo per una fabbrica di lavoratori:

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

Quindi potresti usarlo in questo modo ...

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

MODIFICARE:

Ho appena esteso ulteriormente questa idea per rendere più facile fare la comunicazione tra le corsi: Bridged-worker.js.

EDIT 2:

Il collegamento sopra è a un GIST che ho creato. Qualcun altro in seguito lo ha trasformato in un Repo reale.

I lavoratori Web operano in contesti completamente separati come singoli programmi.

Ciò significa che il codice non può essere spostato da un contesto all'altro in forma di oggetti, poiché sarebbero quindi in grado di fare riferimento agli oggetti tramite chiusure appartenenti all'altro contesto.
Ciò è particolarmente cruciale in quanto ECMASScript è progettato per essere un singolo linguaggio thread e poiché i lavoratori Web operano in thread separati, si assume il rischio di eseguire operazioni non thread-Safe.

Ciò significa di nuovo che i lavoratori web devono essere inizializzati con il codice in forma di origine.

Le specifiche da Whatwg dice

Se l'origine dell'URL assoluto risultante non è la stessa dell'origine dello script di voce, lancia un'eccezione Security_err.

Pertanto, gli script devono essere file esterni con lo stesso schema della pagina originale: non è possibile caricare uno script da un dati: URL o JavaScript: URL e un HTTPS: Pagina non è in grado di avviare i lavoratori usando script con HTTP: URL.

Ma sfortunatamente non spiega davvero perché non si potrebbe consentire di passare una stringa con codice sorgente al costruttore.

un modo migliore da leggere per un lavoratore in linea ..

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

Risposta recente (2018)

Puoi usare Greenlet:

Sposta una funzione asincrona nel proprio thread. Una versione a funzione singola semplificata di Lavoro di lavoro.

Esempio:

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

Prendere la risposta di Adria e metterla in una funzione di copia che funziona con l'attuale Chrome e FF ma non IE10 (il lavoratore di BLOB causa a errore di sicurezza).

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

Ed ecco un esempio funzionante http://jsfiddle.net/ubershmekel/yyzvr/

Dai un'occhiata al plug -in VKThread. Con il plug -in HTIS è possibile svolgere qualsiasi funzione nel codice principale ed eseguirla in un thread (Web Worker). Quindi, non è necessario creare uno speciale "file Web-worker".

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

--Vadim

A seconda del caso d'uso, puoi usare qualcosa di simile

task.js Interfaccia semplificata per l'esecuzione del codice intensivo della CPU su tutti i core (node.js e Web)

Un esempio sarebbe

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

Puoi utilizzare i lavoratori Web nello stesso JavaScript FIE utilizzando i web in linea.

L'articolo seguente ti rivolgerà a comprendere facilmente i webworker e le loro limitazioni e il debug dei webworker.

Mastering in Weborkers

Penso che il modo migliore per farlo sia usare un oggetto BLOB, sotto puoi vedere un semplice esempio.

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

Prova a usare 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

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

})();

Usa il mio piccolo 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);

Quindi penso che abbiamo un'altra bella opzione per questo ora, grazie ai letterali template in ES6. Questo ci consente di rinunciare alla funzione extra lavoratore (e alla sua strana portata) e di scrivere semplicemente il codice destinato al lavoratore come testo multiline per farlo in. Esempio:

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

Ecco un Gist del resto di quell'approccio.

Si noti che possiamo attirare qualsiasi dipendenza extra di funzioni che vogliamo nel lavoratore semplicemente raccogliendole in un array e correndo .ToString su ciascuno di essi per ridurle anche nelle stringhe (dovrebbe funzionare fintanto che sono dichiarazioni di funzione) e Quindi preparalo alla stringa di script. In questo modo non dobbiamo importare che avremmo già potuto raggruppare nell'ambito del codice che stiamo scrivendo.

L'unico vero aspetto negativo di questa particolare versione è che i linter non saranno in grado di lanciare il codice del lavoratore del servizio (poiché è solo una stringa), il che è un vantaggio per l'approccio della funzione del lavoratore separato ".

Questa è solo un'aggiunta sopra: ho dei bei modelli per testare i lavoratori web in JSFiddle. Piuttosto che 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);
  }
})

Normale lavoro web e lavoratore condiviso I modelli sono disponibili.

Ho scoperto che il codepen attualmente non si sintassi in linea <script> tag che non lo sono type="text/javascript" (o che non hanno attributo di tipo).

Quindi ho ideato una soluzione simile ma leggermente diversa usando blocchi etichettati insieme a break, che è l'unico modo in cui puoi salvare da a <script> Tag senza creare una funzione wrapper (che non è necessaria).

<!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 semplice versione promise, Function#callAsWorker, che richiede un thyarg e argomenti (proprio come call) e restituisce una promessa:

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

Uso il codice come questo, puoi definire il tuo onmessage come una funzione diversa dal testo semplice, quindi l'editor può evidenziare il tuo codice e le opere 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;
}

Sì, è possibile, l'ho fatto usando i file BLOB e passando un callback

Ti mostrerò cosa fa una classe che ho scritto e come gestisce l'esecuzione dei callback in background.

Prima di tutto GenericWebWorker con qualsiasi dati che desideri passare al callback che verrà eseguito nel Web Worker, che include le funzioni che si desidera utilizzare, in questo caso un numero, una data e una funzione chiamata blocker

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

Questa funzione bloccante eseguirà un infinito mentre per n milisecondi

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

E poi lo usi in questo modo

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

Ora, è ora di vedere la magia nell'esempio seguente

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

È possibile posizionare il contenuto del file worker.js all'interno dei backticks (che consente una costante di stringa multilinea) e creare il lavoratore da un blob come questo:

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

Questo è utile se per qualsiasi motivo non vuoi avere tag di script separati per il lavoratore.

Un'altra soluzione è solo quella di avvolgere il lavoratore in una funzione, quindi creare una chiazza che invoca la funzione in questo modo:

     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.
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top