web-dev-qa-db-fra.com

Travailleurs Web sans fichier Javascript séparé?

Autant que je sache, les travailleurs Web doivent être écrits dans un fichier JavaScript séparé, et s'appeler ainsi: 

new Worker('longrunning.js')

J'utilise le compilateur de fermeture pour combiner et minimiser tout mon code source JavaScript, et je préférerais ne pas avoir à placer mes travailleurs dans des fichiers séparés pour la distribution. Y a-t-il un moyen de faire ça?

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

Étant donné que les fonctions de première classe sont si essentielles pour JavaScript, pourquoi la méthode standard de travail en arrière-plan doit-elle charger un fichier JavaScript complet à partir du serveur?

255
Ben Dilts

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

Que faire si vous voulez créer votre script de travail à la volée ou créer une page autonome sans avoir à créer des fichiers de travail séparés? Avec Blob (), vous pouvez "incorporer" votre opérateur dans le même fichier HTML que votre logique principale en créant un handle URL vers le code de l'opérateur sous forme de chaîne


Exemple complet de travailleur en ligne 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>

191
vsync

La solution html5rocks consistant à incorporer le code de Web worker au format HTML est assez horrible.
Et une goutte de code JavaScript en tant que chaîne échappé n'est pas meilleure, notamment parce qu'elle complique le travail (le compilateur Closure ne peut pas fonctionner sur des chaînes).

Personnellement, j’aime beaucoup les méthodes toString, mais @ dan-man QUE regex! 

Mon approche préférée:

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

Le support est l'intersection de ces trois tables:

Toutefois, cela ne fonctionnera pas pour un SharedWorker, car l'URL doit correspondre exactement, même si le paramètre facultatif 'name' correspond. Pour un SharedWorker, vous aurez besoin d'un fichier JavaScript séparé.


Mise à jour 2015 - La singularité ServiceWorker arrive

Maintenant, il existe un moyen encore plus puissant de résoudre ce problème . Encore une fois, stockez le code travailleur en tant que fonction (plutôt qu'une chaîne statique) et convertissez-le à l'aide de .toString (), puis insérez le code dans CacheStorage sous une URL statique. de votre choix.

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

Il y a deux solutions possibles. ObjectURL comme ci-dessus, ou plus simplement, mettez un fichier JavaScript real à /my_workers/worker1.js

Les avantages de cette approche sont les suivants:

  1. SharedWorkers peut également être pris en charge.
  2. Les onglets peuvent partager une seule copie en cache à une adresse fixe. L'approche blob fait proliférer des objectURL aléatoires pour chaque onglet.
139
Adria

Vous pouvez créer un fichier JavaScript unique tenant compte de son contexte d'exécution et pouvant faire office de script parent et de travailleur. Commençons par une structure de base pour un fichier comme celui-ci:

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

Comme vous pouvez le constater, le script contient tout le code du point de vue du parent et du travailleur, en vérifiant si sa propre instance individuelle est un travailleur avec !document. Le calcul un peu compliqué script_path permet de calculer avec précision le chemin du script par rapport à la page parent, car le chemin fourni à new Worker est relatif à la page parent et non au script.

35
Delan Azabani

En utilisant la méthode Blob, que diriez-vous de cela pour une fabrique de travailleurs:

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

Donc, vous pouvez l'utiliser comme ça ...

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

MODIFIER:

Je viens d'élargir cette idée pour faciliter la communication entre threads: bridged-worker.js .

EDIT 2: 

Le lien ci-dessus est un Gist que j'ai créé. Quelqu'un d'autre l'a transformé plus tard en un repo réel .

25
dan-man

Les travailleurs Web travaillent dans des contextes entièrement séparés en tant que programme individuel.

Cela signifie que le code ne peut pas être déplacé d'un contexte à un autre sous forme d'objet, car ils seraient alors en mesure de référencer des objets via des fermetures appartenant à l'autre contexte.
Ceci est particulièrement important car ECMAScript est conçu pour être un langage à thread unique et, comme les travailleurs Web fonctionnent dans des threads distincts, vous courriez alors le risque que des opérations sans thread-thread soient effectuées. 

Cela signifie encore une fois que les travailleurs Web doivent être initialisés avec du code sous forme source.

La spécification de WHATWG dit

Si l’origine du résultat L'URL absolue n'est pas la même chose que le Origine du script d'entrée, puis jetez une exception SECURITY_ERR.

Ainsi, les scripts doivent être des fichiers externes avec le même schéma que l'original page: vous ne pouvez pas charger un script à partir d'un fichier data: URL ou javascript: URL et un https: la page n'a pas pu démarrer les travailleurs en utilisant des scripts avec http: URL.

mais malheureusement, cela n'explique pas vraiment pourquoi on n'aurait pas pu autoriser le passage d'une chaîne avec du code source au constructeur.

11
Sean Kinsey

un meilleur moyen de lire pour un travailleur en ligne ..

    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"); 
6
Chris Tobba

Prendre la réponse d'Adria et la placer dans une fonction de copier-coller qui fonctionne avec Chrome et FF actuels, mais pas avec IE10 (le travailleur de blob provoque une erreur security ).

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

Et voici un exemple de travail http://jsfiddle.net/ubershmekel/YYzvr/

4
ubershmekel

Réponse récente (2018)

Vous pouvez utiliser Greenlet :

Déplacez une fonction asynchrone dans son propre thread. Une version simplifiée à fonction unique de Workerize .

Exemple:

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'))
4
GG.

Jetez un coup d'oeil au plugin vkThread. Avec htis plugin, vous pouvez utiliser n'importe quelle fonction de votre code principal et l'exécuter dans un thread (WebWork) Vous n'avez donc pas besoin de créer un "fichier Web-worker" spécial.

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

--Vadim

2
vadimk

En fonction de votre cas d'utilisation, vous pouvez utiliser quelque chose comme

task.js Interface simplifiée permettant d'exécuter du code gourmand en ressources CPU sur tous les cœurs (node.js et Web)

Un exemple serait

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
});
2
Chad Scira

Vous pouvez utiliser des travailleurs Web dans le même fichier JavaScript en utilisant des travailleurs Web en ligne.

L'article ci-dessous s'adressera à vous pour comprendre facilement les travailleurs Web et leurs limites et le débogage des travailleurs Web.

Maîtrise des webworkers

1
kirankumar

Je pense que la meilleure façon de faire est d'utiliser un objet Blob, vous pouvez voir ci-dessous un exemple 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"); 
1
Miguel Q.

Une version promisifiée simple, Function#callAsWorker, qui prend un thisArg et des arguments (comme call), et renvoie une promesse:

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
trincot

Essayez d'utiliser 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
1

Ceci est juste un ajout à ce qui précède - j'ai un beau modèle pour tester les travailleurs Web dans jsFiddle. Plutôt que Blob, il utilise 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 travailleur Web et travailleur partagé les modèles sont disponibles.

1
Tomáš Zato

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
hamboy75

Donc, je pense que nous avons une autre option intéressante pour cela maintenant, grâce aux littéraux de modèles dans ES6. Cela nous permet de nous passer de la fonction de travail supplémentaire (et de sa portée étrange) et d'écrire simplement le code destiné au travail sous forme de texte multiligne, un peu comme dans le cas où nous utilisions le texte, mais sans avoir besoin d'un document ou d'un DOM faire cela en. Exemple: 

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

Voici un Résumé du reste de cette approche .

Notez que nous pouvons insérer toutes les dépendances de fonction supplémentaires souhaitées dans le serveur simplement en les rassemblant dans un tableau et en exécutant .toString sur chacun d’eux pour les réduire également (il convient que ces déclarations soient des déclarations de fonction) et puis juste en ajoutant cela à la chaîne de script. De cette façon, nous n'avons pas à importer des scripts que nous avons peut-être déjà intégrés à la portée du code que nous écrivons.

Le seul inconvénient de cette version est que les linters ne pourront pas imiter le code de l'opérateur de service (puisqu'il ne s'agit que d'une chaîne de caractères), ce qui constitue un avantage pour "la fonction de travail séparé". 

1
Dtipson

ici 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);
});
1
59naga

Utiliser mon petit 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);
1
zevero

J'ai découvert qu'actuellement CodePen ne met pas en surbrillance les balises <script> en ligne qui ne sont pas type="text/javascript" (ou qui n'ont pas d'attribut type).

J'ai donc imaginé une solution similaire mais légèrement différente en utilisant blocks étiquetés avec break, ce qui est le seul moyen de sortir d'une balise <script> sans créer de fonction d'encapsulation (inutile).

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

J'utilise un code comme celui-ci, vous pouvez définir votre message comme une fonction autre que le texte brut, afin que l'éditeur puisse mettre en évidence votre code et les travaux 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;
}

0
Z.JC

Oui, c'est possible, je l'ai fait en utilisant des fichiers Blob et en passant un rappel

Je vais vous montrer ce que fait une classe que j'ai écrite et comment elle gère l'exécution des rappels en arrière-plan.

Commencez par instancier la GenericWebWorker avec les données que vous souhaitez transmettre au callback à exécuter dans le Web Worker, y compris les fonctions que vous souhaitez utiliser, dans ce cas un nombre, une date et une fonction appelée blocker

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

Cette fonction de blocage exécutera un infini tant que pendant n millisecondes

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

et puis vous l'utilisez comme ça

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

Maintenant, le temps de voir la magie dans l'exemple ci-dessous

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

Vous pouvez placer le contenu de votre fichier worker.js dans des backticks (ce qui permet une constante de chaîne multiligne) et créer le worker à partir d'un blob comme celui-ci:

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

C'est pratique si, pour une raison quelconque, vous ne souhaitez pas que les balises de script soient séparées pour le travailleur.

0
samgak

Une autre solution consiste simplement à envelopper le travailleur dans une fonction, puis à créer un blob invoquant la fonction comme suit:

     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.
0
Shlomi Schwartz