Cette traduction est en cours.

Les Web Workers fournissent un moyen simple d'exécuter des scripts dans des threads en arrière-plan d'un contenu web. Le thread worker peut réaliser des tâches sans interférer avec l'interface utilisateur. De plus, ils peuvent réaliser des E/S en utilisant XMLHttpRequest (même si les attributs responseXML  et channel restent toujours null). Une fois créé, un worker peut envoyer des messages à son thread parent par l'intermédiaire du gestionnaire d'événement spécifié à sa création. Cet article propose une introduction à l'utilisation des web workers.

API  Web Workers

Un worker est un objet créé par un constructeur (ex: Worker()) qui s'execute dans un fichier javascript — ce fichier contient du code qui s'executera dans le processus de worker; Les workers s'exécutent dans un autre contexte global différent du contexte de la window (fenêtre) courante. Ainsi, utiliser le raccourci window pour obtenir le contexte global courant (au lieu de self) au sein d'un Worker retournera une erreur.

Le contexte de worker est représenté par un objet DedicatedWorkerGlobalScope dans le cas de workers dédiés (les workers standards qui sont utilisés par un seul script) ; les workers partagés utilisent  SharedWorkerGlobalScope). Un worker dédié est seulement accessible depuis le script qui l'a appelé, tandis que les workers partagés sont accessibles depuis plusieurs scripts.

Note : consultez la page d'introduction à l'API Web Workers pour la documentation de référence sur les workers et des guides complémentaires.

Vous pouvez exécuter n'importe quel code que vous aimez dans le worker thread , à quelques exceptions près. Par exemple, vous ne pouvez pas manipuler directement les DOM à l'intérieur d'un worker, ou utiliser des méthodes par défaut et des propriétés de l'objet  window . Mais vous pouvez utiliser un grand nombre d'éléments disponibles sous window , y compris WebSockets, et les mécanismes de stockage de données comme IndexedDB  et l' API Store Data de Firefox OS-only. Voir  Fonctions et classes disponibles dans les web workers  pour plus de détails.

Les données sont envoyées entre les workers et le thread principal via un système de messages  — les deux parties envoient leurs messages à l'aide de la méthode  postMessage() et répondent au message via le gestionnaire d'évènements onmessage (le message est contenu par l'attribut Message ). Les données sont copiées plutôt que partagées.

Les workers peuvent, à leur tour, générer de nouveaux workers, tant qu'ils sont hébergés dans la même origine que la page parent. En outre, les workers peuvent utiliser  XMLHttpRequest  pour le réseau d'E/S, avec les exceptions des attributs responseXML  et channel sur  XMLHttpRequest  qui retournent toujours  null.

Workers dédiés

Comme précisé au-dessus, un worker dédié est accessible uniquement par le script qui l'a appelé. Dans cette section, nous utiliserons le JavaScript trouvé dans notre exemple simple de worker dédié (run dedicated worker). Il vous permet d'entrer deux nombres à multiplier ensemble. Les numéros sont envoyés à un worker dédié, multipliés, et le résultat est retourné à la page et affiché.

Cet exemple est plutôt banal, mais nous avons décidé de le simplifier tout en vous présentant les concepts fondamentaux des workers. Les détails plus avancés sont abordés plus loin dans l'article.

Détection des fonctionnalités d'un worker

Afin de mieux contrôler les erreurs et la rétrocompatiblité, une bonne idée est d'envelopper votre code de worker de cette manière (main.js):

if (window.Worker) {

  ...

}

Génération d'un worker dédié

Créer un nouveau worker est simple. Vous devez juste appeler le constructeur Worker() , en spécifiant l'URI du script à exécuter dans le thread du worker (main.js) :

 
var myWorker = new Worker('worker.js');

Envoi de messages depuis et vers un worker dédié

La communication des workers se fait via la méthode postMessage() et le gestionnaire d'événement onmessage. Lorsque vous voulez envoyer un message au worker, vous postez des messages comme ceci ( main.js ) :

first.onchange = function() {
  myWorker.postMessage([first.value,second.value]);
  console.log('Message posted to worker');
}

second.onchange = function() {
  myWorker.postMessage([first.value,second.value]);
  console.log('Message posted to worker');
}

Nous avons donc ici deux éléments <input> représentés par les variables first et second; lorsque la valeur de l'une est modifiée, myWorker.postMessage([first.value,second.value]) est utilisé pour envoyer les valeurs au worker, comme ici, un tableau. Vous pouvez envoyer tout type de message.

Dans le worker, nous pouvons répondre, lorsque le message est reçu, avec un gestionnaire d'événement comme celui-ci ( worker.js ):

onmessage = function(e) {
  console.log('Message received from main script');
  var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
  console.log('Posting message back to main script');
  postMessage(workerResult);
}

Le gestionnaire onmessage nous permet d'exécuter du code à chaque fois qu'un message est reçu, le message lui-même étant disponible  dans l'attribut data de l'évènement message. Ici, nous multiplions simplement les deux nombres ensemble et nous utilisons de nouveau postMessage() pour poster le résultat vers le thread principal.

De retour dans le thread principal, nous utilisons à nouveau onmessage, pour répondre au message renvoyé par le worker:

myWorker.onmessage = function(e) {
  result.textContent = e.data;
  console.log('Message received from worker');
}

Ici, nous récupérons les données avec l'événement onmessage et avec, nous définissons l'attribut textContent du paragraphe result, afin que l'utilisateur puisse voir le résultat du calcul.

Note : L'URI passé en paramètre au constructeur Worker doit obéir à la same-origin policy .

Il y a actuellement un désaccord entre differents navigateurs sur l'origine des URI ; Gecko 10,0 (Firefox 10.0 / Thunderbird 10.0 / SeaMonkey 2.7) et supérieur autorisent les URI de donnée et Internet Explorer 10 n'autorise pas les URI de Blob comme un script valide pour les workers.

Note : onmessage et postMessage() sont accrochés à l'objet Worker quand il est utilisé dans le script principal, mais pas lorsqu'il est utilisé dans le worker, car le worker est de portée globale.
Note : Quand un message est passé entre thread principal et worker, il est copié ou «transféré» (déplacé), pas partagé. Lire Transferring data to and from workers: further details   pour une explication beaucoup plus approfondie .

Arrêt d'un worker

Si vous avez besoin d'arrêter immédiatement un worker en cours d'exécution depuis le thread principal, vous pouvez le faire en appelant la méthode de worker terminate():

myWorker.terminate();
 

Le worker est stoppé immédiatement, sans possibilité de terminer ses opérations ou de nettoyer.

Les workers peuvent se fermer en appelant leur propre méthode close:

close();

Gestion des erreurs

Quand une erreur d'exécution se produit dans le worker, son gestionnaire d'événements onerror est appelé. Il reçoit un événement nommé error qui implémente l'interface ErrorEvent.

L'événement ne se propage pas et est annulable ; pour empêcher l'action par défaut d'avoir lieu, le worker peut appeler la méthode preventDefault() .

L'événement d'erreur possède les trois champs suivants qui sont intéressants :

message
Un message d'erreur lisible par un humain.
filename
Le nom du fichier de script dans lequel l'erreur est survenue.
lineno
Le numéro de la ligne du fichier de script sur laquelle l'erreur est survenue.

Génération de Subworkers

Un worker peut engendrer plusieurs workers. Les subworkers doivent être hébergés dans le même dossier que la page parent. En outre, les URI des subworkers sont résolus par rapport à l'emplacement du worker parent plutôt que celui de la page. Cela rend plus facile pour les subworkers de garder une trace de leurs dépendances.

Importation de scripts et bibliothèques

Les threads de worker ont accès à une fonction globale, importScripts(), ce qui leur permet d'importer des scripts. Il accepte zéro ou plusieurs URI comme paramètres de ressources pour l'importation; tous les exemples suivants sont valables:

importScripts();                         /* n'importe rien */
importScripts('foo.js');                 /* importe juste "foo.js" */
importScripts('foo.js', 'bar.js');       /* importe deux scripts */
importScripts('//example.com/hello.js'); /* Vous pouvez importer des scripts depuis d'autres origines */

Le navigateur charge chaque script répertorié et l'exécute. Tous les objets globaux de chaque script peuvent ensuite être utilisés par le worker. Si le script ne peut pas être chargé, NETWORK_ERROR est émit, et le code qui suit ne sera pas exécuté. Le code exécuté précédemment (y compris le code reporté en utilisant window.setTimeout()) reste fonctionnel. Les déclarations de fonctions après la méthode importScripts() sont également conservées, puisque celles-ci sont toujours évaluées avant le reste du code.

Note: Les scripts peuvent être téléchargés dans n'importe quel ordre, mais ils seront exécutés dans l'ordre dans lequel vous passez les noms des fichiers dans importScripts(). Cela se fait de façon synchrone ; importScripts() ne retourne rien tant que tous les scripts n'ont pas été chargés et exécutés.

Workers partagés

Un worker partagé est accessible par plusieurs scripts - y compris de différentes fenêtres, des iframes ou même d'autres workers - tant qu'ils ont la même origine. Dans cette section, nous allons discuter du code JavaScript tiré de l'exemple Basic shared workeril est très semblable à l'exemple de base du worker dédié, à ceci près qu'il dispose de deux fonctions disponibles pour différents fichiers de script : multiplier deux nombres ou quadriller un nombre. Les deux scripts utilisent le même worker pour effectuer le calcul réel requis.

Nous nous concentrerons ici sur les différences entre workers dédiés et partagés. Notez que dans cet exemple, nous avons deux pages HTML, chacune avec JavaScript appliqué qui utilise le même fichier worker unique.

Note : Si SharedWorker peut être consulté à partir de plusieurs contextes de navigation, tous doivent partager exactement la même origine (mêmes protocole, hôte et port).

Note : Dans Firefox, les workers partagés ne peuvent pas être partagés entre documents téléchargés de fenêtres privées et non privées (bug 1177621).

Génération d'un worker partagé

C'est la même démarche que pour un worker dédié, mais avec un nom de constructeur différent (voir index.html et index2.html ) - chacun doit déclarer le worker à l'aide du code suivant :

var myWorker = new SharedWorker("worker.js");

Une grande différence réside dans la communication via un objet port - un port est explicitement ouvert pour que les scripts puissent communiquer avec le worker (cela se fait implicitement dans le cas des workers dédiés ).

Le port doit être démarré implicitement par l'utilisation du gestionnaire d'évènement  onmessageou explicitement avec start() avant que les messages puissent être postés. Bien que les fichiers  multiply.js et worker.js dans la démo appellent actuellement la méthode start() , ces appels ne sont plus nécessaires depuis l'utilisation du gestionnaire d'évènements onmessage . L'appel de start() est nécessaire seulement si l'évènement message  est câblé via la méthode addEventListener() .

Lors de l'utilisation de la méthode start()  pour ouvrir la connexion du port, il faut l'appeler à la fois du thread parent et du thread du worker si une communication bidirectionnelle est nécessaire.

myWorker.port.start();  // appelé dans le thread parent

port.start();  //appelé dans le thread du worker, en supposant que la variable port fait référence à un port.

Envoi de messages depuis et vers un worker partagé

Maintenant, les messages peuvent être envoyés au worker comme avant, mais la méthode postMessage() doit être invoquée à travers l'objet port (encore une fois, vous verrez des constructions similaires dans les scripts multiply.js et square.js ):

squareNumber.onchange = function() {
  myWorker.port.postMessage([squareNumber.value,squareNumber.value]);
  console.log('Message posted to worker');
}

Du coté worker, c'est un peu plus de complexe ( worker.js ):

onconnect = function(e) {
  var port = e.ports[0];

  port.onmessage = function(e) {
    var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
    port.postMessage(workerResult);
  }
}

Tout d'abord, nous utilisons le gestionnaire d'évènement onconnect pour déclencher le code lorsqu'une connexion au port se produit (c'est-à-dire, lorsque le gestionnaire d'événements onmessage dans le thread parent est configuré ou lorsque la méthode start() est invoquée explicitement dans le thread parent).

Nous utilisons l'attribut port de cet objet événement pour saisir le port et le stocker dans une variable.

Ensuite, nous utilisons un gestionnaire message sur le port pour faire les calculs et retourner le résultat au thread principal. La mise en place du gestionnaire message dans le thread worker ouvre également implicitement la connexion retour au port du thread parent, de sorte que l'appel par  port.start() n'est pas nécessaire actuellement, comme indiqué plus haut.

Enfin, de retour dans le script principal, nous traitons le message (de nouveau, vous verrez des constructions similaires dans les scripts multiply.js et square.js ) :

myWorker.port.onmessage = function(e) {
  result2.textContent = e.data[0];
  console.log('Message received from worker');
}

Quand un message revient via le port du worker, nous vérifions ce type de résultat, puis nous l'insérons dans le paragraphe résultat approprié.

A propos de la sécurité du thread

L'interface Worker engendre des pérturbations au niveau du système d'exploitation et des programmeurs conscients peuvent craindre les effets «intéressants» des interventions de la concurrence dans le code s'ils n'y font pas attention.

.Cependant, puisque les workers contrôlent soigneusement les points de communication avec d'autres threads, il est en réalité très difficile de déclencher des problèmes de concurrence. Il n'y a pas de moyen d'accéder aux composants " non-threadsafe" , ou au DOM. Et vous devez passer des données spécifiques dans et hors d'un thread via des objets sérialisés. Donc, vous devez travailler très dur pour causer des problèmes dans votre code.

Un exemple de catastrophe serait ce qui suit :

Contenu HTML

<html>
<head>
<title>Multithreading Catastrophy</title>
<style>
body { margin: 0px; }
canvas { position: absolute; top: 0; bottom: 0; left: 0; right:0; width: 100%; height: 100%; }
</style>
<script src="main.js" async></script>
</head>
<body>
<canvas id="canvas"></canvas>
</body>
</html>

Contenu main.js

// main.js
var myworker = new Worker("worker.js"), width=window.innerWidth, height=window.innerHeight, context=document.getElementById('canvas').getContext('2d');
var imagedatatmp=context.createImageData(width,height);

myworker.onmessage = function(data){
    imageData = imagedatatmp.from(data);
};

setTimeout(function draw_canvas() {
    context.putImageData(imageData);
    setTimeout(draw_canvas, 1000/60);
},10);

window.onresize = window.reload; // Quick (to type) n' dirty way to resize;

Contenu worker.js

// worker.js
window.onmessage = function(width, height){
var noise = function(x, y, z) {
    var p = new Array(512), permutation = [151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180];
    for (var i = 0;i < 256;i++) p[256 + i] = p[i] = permutation[i];
    var X = Math.floor(x) & 255, Y = Math.floor(y) & 255, Z = Math.floor(z) & 255; x -= Math.floor(x), y -= Math.floor(y), z -= Math.floor(z);
    var u = fade(x), v = fade(y), w = fade(z);
    var A = p[X] + Y, AA = p[A] + Z, AB = p[A + 1] + Z, B = p[X + 1] + Y, BA = p[B] + Z, BB = p[B + 1] + Z;
    return scale(lerp(w, lerp(v, lerp(u, grad(p[AA], x, y, z), grad(p[BA], x - 1, y, z)), lerp(u, grad(p[AB], x, y - 1, z), grad(p[BB], x - 1, y - 1, z))), lerp(v, lerp(u, grad(p[AA + 1], x, y, z - 1), grad(p[BA + 1], x - 1, y, z - 1)), lerp(u, grad(p[AB + 1], x, y - 1, z - 1), grad(p[BB + 1], x - 1, y - 1, z - 1)))));
};
  function fade(t) { return t * t * t * (t * (t * 6 - 15) + 10); }
  function lerp(t, a, b) { return a + t * (b - a); }
  function grad(hash, x, y, z) {
    var h = hash & 15; var u = h < 8 ? x : y, v = h < 4 ? y : h == 12 || h == 14 ? x : z;
    return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
  }
  function scale(n) { return (1 + n) / 2; }
var length = width*height; var canvasnoisedata=new UInt32Array(length);

setTimeout(function make_noise() {
var i=length, z=Math.random()*1024;
  while ( i-- ) {
      canvasnoisedata[i] = noise(i%width+z,i/width+z,z);
  }
  setTimeout(make_noise, 1000/60);
},1000/60);

setTimeout(function post_noise() {
    postMessage( canvasnoisedata );
    setTimeout(post_noise, 1000/60);
},1000/60);
};

Résultat

Politique de sécurité du contenu

Les workers sont considérés comme ayant leur propre contexte d'exécution, distinct du document qui les a créés. Pour cette raison, ils ne sont en général pas régis par la  police de sécurité du contenu   du document (ou du worker parent) qui l'a créé. Ainsi par exemple, supposons un document fourni avec l'en-tête suivant :

Content-Security-Policy: script-src 'self'

Entre autres choses, cela empêchera les scripts qu'il inclut d'utiliser  eval().   Toutefois, si le script construit un worker, le code exécuté dans le contexte du worker sera autorisé à utiliser  eval().

Pour spécifier une politique de sécurité du contenu pour le worker, définissez un en-tête de réponse  Police-Sécurité-Contenu pour les demandes qui livrent le script du worker lui-même.

Par exception, quand l'origine du script du worker est un identifiant globalement unique (par exemple, si son URL a un schéma de données ou de blob), le worker hérite du CSP du document ou du worker qui l'a créé.

Transfert de données vers et depuis les workers : plus de détails

Les données transmises entre la page principale et les workers sont copiées, non partagées. Les objets sont sérialisés lorsqu'ils sont remis au worker et, par la suite, dé-sérialisés à l'autre extrémité. La page et le worker ne partagent pas la même instance, de sorte que le résultat final est qu'un duplicata est créé à chaque extrémité. La plupart des navigateurs implémentent cette fonctionnalité comme un clônage structuré .

Pour illustrer cela, créons pour objet didactique une fonction nommée  emulateMessage(), avec laquelle nous allons simuler le comportement d'une valeur clônée et non partagée pendant le passage du worker à la page principale et vice-versa :

function emulateMessage(vVal) {
    return eval('(' + JSON.stringify(vVal) + ')');
}

// Tests

// test #1
var example1 = new Number(3);
console.log(typeof example1); // object
console.log(typeof emulateMessage(example1)); // number

// test #2
var example2 = true;
console.log(typeof example2); // boolean
console.log(typeof emulateMessage(example2)); // boolean

// test #3
var example3 = new String('Hello World');
console.log(typeof example3); // object
console.log(typeof emulateMessage(example3)); // string

// test #4
var example4 = {
    'name': 'John Smith',
    "age": 43
};
console.log(typeof example4); // object
console.log(typeof emulateMessage(example4)); // object

// test #5
function Animal(sType, nAge) {
    this.type = sType;
    this.age = nAge;
}
var example5 = new Animal('Cat', 3);
alert(example5.constructor); // Animal
alert(emulateMessage(example5).constructor); // Object

Une valeur qui est clônée et non partagée est appelée message. Comme vous le savez probablement maintenant, les messages peuvent être envoyés vers et à partir du thread principal en utilisant  postMessage() et le message attribut de l'évènement data  contient des données transmises par le worker.

exemple.html (la page principale) :

var myWorker = new Worker('my_task.js');

myWorker.onmessage = function(oEvent) {
  console.log('Worker said : ' + oEvent.data);
};

myWorker.postMessage('ali');

my_task.js (le worker) :

postMessage("I\'m working before postMessage(\'ali\').");

onmessage = function(oEvent) {
  postMessage('Hi ' + oEvent.data);
};

L'algorithme clônage structuré peut accepter JSON et quelques choses qui échappent à JSON comme les références circulaires.

Exemples de passages de données

Exemple #1 : Création d'un générique "asynchronous eval()"

L'exemple suivant montre comment utiliser un worker pour exécuter de manière asynchrone tout code JavaScript autorisé dans un worker, à travers l' eval()  qu'il contient.

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

L'URL des données est équivalente à une demande de réseau, avec la réponse suivante :

onmessage = function(oEvent) {
  postMessage({
    'id': oEvent.data.id,
    'evaluated': eval(oEvent.data.code)
  });
}

Exemple d'utilisation :

// asynchronous alert message...
asyncEval('3 + 2', function(sMessage) {
    alert('3 + 2 = ' + sMessage);
});

// asynchronous print message...
asyncEval("\"Hello World!!!\"", function(sHTML) {
    document.body.appendChild(document.createTextNode(sHTML));
});

// asynchronous void...
asyncEval("(function () {\n\tvar oReq = new XMLHttpRequest();\n\toReq.open(\"get\", \"http://www.mozilla.org/\", false);\n\toReq.send(null);\n\treturn oReq.responseText;\n})()");

Exemple #2 : Passage avancé des données JSON et création d'un système de commutation

Si vous devez transmettre des données complexes et appeler plusieurs fonctions différentes à la fois sur la page principale et sur le worker, vous pouvez créer un système qui regroupe tout ensemble.

Tout d'abord, nous créons une classe "QueryableWorke"r qui prend l'URL du worker, un auditeur par défaut et un gestionnaire d'erreur, et cette classe va suivre une liste d'auditeurs et nous aider à communiquer avec le worker :

function QueryableWorker(url, defaultListener, onError) {
    var instance = this,
        worker = new Worker(url),
        listeners = {};

    this.defaultListener = defaultListener || function() {};
    
    if (onError) {worker.onerror = onError;}
    
    this.postMessage = function(message) {
        worker.postMessage(message);
    }

    this.terminate = function() {
        worker.terminate();
    }
}

Ensuite, nous ajoutons les méthodes d'ajout / suppression d'auditeurs:

this.addListeners = function(name, listener) {
    listeners[name] = listener;
}

this.removeListeners = function(name) {
    delete listeners[name];
}

Ici, nous laissons le worker manipuler deux opérations simples à titre d'illustration : calculer la différence entre deux nombres et faire une alerte après trois secondes. Pour ce faire, nous mettons d'abord en œuvre une méthode "sendQuery" qui demande si le worker possède effectivement les méthodes nécessaires pour faire ce que nous voulons.

* 
  Cette fonction prend au-moins un argument, le nom de la méthode que nous voulons interroger.
  Ensuite, nous pourrons passer les arguments dont la méthode a besoin.
 */
this.sendQuery = function() {
    if (arguments.length < 1) {
         throw new TypeError('QueryableWorker.sendQuery takes at least one argument'); 
         return;
    }
    worker.postMessage({
        'queryMethod': arguments[0],
        'queryArguments': Array.prototype.slice.call(arguments, 1)
    });
}

Nous terminons "QueryableWorker" avec la méthode onmessage . Si le worker a les méthodes interrogées, il doit renvoyer le nom de l'auditeur correspondant et les arguments dont il a besoin, il suffit de le trouver dans les auditeurs:

worker.onmessage = function(event) {
    if (event.data instanceof Object &&
        event.data.hasOwnProperty('queryMethodListener') &&
        event.data.hasOwnProperty('queryMethodArguments')) {
        listeners[event.data.queryMethodListener].apply(instance, event.data.queryMethodArguments);
    } else {
        this.defaultListener.call(instance, event.data);
    }
}

Maintenant sur le worker. Tout d'abord, nous devons avoir les méthodes pour gérer les deux opérations simples:

var queryableFunctions = {
    getDifference: function(a, b) {
        reply('printStuff', a - b);
    },
    waitSomeTime: function() {
        setTimeout(function() {
            reply('doAlert', 3, 'seconds');
        }, 3000);
    }
}

function reply() {
    if (arguments.length < 1) { 
        throw new TypeError('reply - takes at least one argument'); 
        return; 
    } 
    postMessage({ 
        queryMethodListener: arguments[0], 
        queryMethodArguments: Array.prototype.slice.call(arguments, 1) 
    });
}

/*  Cette méthode est appelée lorsque la page principale appelle la méthode postMessage de QueryWorker directement */
function defaultReply(message) {
    // faire quelque chose
}

Et la méthode onmessage est maintenant banale :

onmessage = function(event) {
    if (event.data instanceof Object &&
        event.data.hasOwnProperty('queryMethod') &&
        event.data.hasOwnProperty('queryMethodArguments')) {
        queryableFunctions[event.data.queryMethod]
            .apply(self, event.data.queryMethodArguments);
    } else {
        defaultReply(event.data);
    }
}

Voici la mise en oeuvre complète :

example.html (la page principale):

<!doctype html>
  <html>
    <head>
      <meta charset="UTF-8"  />
      <title>MDN Example - Queryable worker</title>
    <script type="text/javascript">
    /*
      Méthodes d'instances QueryableWorker:
        * sendQuery(nom de la fonction de consultation, argument à passer 1, argument à passer 2, etc.): appelle une fonction de consultation de worker
        * postMessage(chaîne de caractère ou donnée JSON): voir Worker.prototype.postMessage()
        * terminate(): le worker se termine
        * addListener(name, function): ajoute un auditeur
        * removeListener(name): supprime un auditeur
      Propriétés d'instances QueryableWorker :
        * defaultListener: l'auditeur par défaut exécuté seulement si le worker appelle la fonction postMessage() directement
     */
    function QueryableWorker(url, defaultListener, onError) {
      var instance = this,
      worker = new Worker(url),
      listeners = {};

      this.defaultListener = defaultListener || function() {};
 
      if (onError) {worker.onerror = onError;}

      this.postMessage = function(message) {
        worker.postMessage(message);
      }

      this.terminate = function() {
        worker.terminate();
      }

      this.addListener = function(name, listener) {
        listeners[name] = listener; 
      }
 
      this.removeListener = function(name) { 
        delete listeners[name];
      }

      /* 
        Cette fonction prend au-moins un argument, le nom de la méthode que nous voulons consulter.
        Ensuite, nous pourrons passer les arguments dont la méthode a besoin.
      */
      this.sendQuery = function() {
        if (arguments.length < 1) {
          throw new TypeError('QueryableWorker.sendQuery takes at least one argument'); 
          return;
        }
        worker.postMessage({
          'queryMethod': arguments[0],
          'queryMethodArguments': Array.prototype.slice.call(arguments, 1)
        });
      }

      worker.onmessage = function(event) {
        if (event.data instanceof Object &&
          event.data.hasOwnProperty('queryMethodListener') &&
          event.data.hasOwnProperty('queryMethodArguments')) {
          listeners[event.data.queryMethodListener].apply(instance, event.data.queryMethodArguments);
        } else {
          this.defaultListener.call(instance, event.data);
        }
      }
    }

    // votre "queryable" worker personnalisé
    var myTask = new QueryableWorker('my_task.js');

    // votre "listeners" personnalisé
    myTask.addListener('printStuff', function (result) {
      document.getElementById('firstLink').parentNode.appendChild(document.createTextNode('The difference is ' + result + '!'));
    });

    myTask.addListener('doAlert', function (time, unit) {
      alert('Worker waited for ' + time + ' ' + unit + ' :-)');
    });
</script>
</head>
<body>
  <ul>
    <li><a id="firstLink" href="javascript:myTask.sendQuery('getDifference', 5, 3);">What is the difference between 5 and 3?</a></li>
    <li><a href="javascript:myTask.sendQuery('waitSomeTime');">Wait 3 seconds</a></li>
    <li><a href="javascript:myTask.terminate();">terminate() the Worker</a></li>
  </ul>
</body>
</html>

my_task.js (le worker):

var queryableFunctions = {
  // exemple #1: calculer la différence entre deux nombres:
  getDifference: function(nMinuend, nSubtrahend) {
      reply('printStuff', nMinuend - nSubtrahend);
  },
  // exemple #2: attendre trois secondes
  waitSomeTime: function() {
      setTimeout(function() { reply('doAlert', 3, 'seconds'); }, 3000);
  }
};

// fonctions système

function defaultReply(message) {
  // votre fonction PUBLIC par défaut exécutée seulement si la page principale appelle directement la méthode queryableWorker.postMessage()
  // faire quelque chose
}

function reply() {
  if (arguments.length < 1) { throw new TypeError('reply - not enough arguments'); return; }
  postMessage({ 'queryMethodListener': arguments[0], 'queryMethodArguments': Array.prototype.slice.call(arguments, 1) });
}

onmessage = function(oEvent) {
  if (oEvent.data instanceof Object && oEvent.data.hasOwnProperty('queryMethod') && oEvent.data.hasOwnProperty('queryMethodArguments')) {
    queryableFunctions[oEvent.data.queryMethod].apply(self, oEvent.data.queryMethodArguments);
  } else {
    defaultReply(oEvent.data);
  }
};

Il est possible de modifier le contenu de chaque page principale -> worker et worker -> message de la page principale. Et les noms de propriété "queryMethod", "queryMethodListeners", "queryMethodArguments" peuvent exister aussi longtemps qu'ils sont cohérents dans QueryableWorker et le worker.

Passage des données en transférant la propriété (objets transférables)

Google Chrome 17+ et Firefox 18+ contiennent un moyen supplémentaire de transmettre certains types d'objets (objets transférables, c'est-à-dire les objets implémentant l'interface Transferable vers ou à partir d'un worker à haute performance. Les objets transférables sont transférés d'un contexte à l'autre avec une opération de copie zéro, ce qui entraîne une amélioration considérable de la performance lors de l'envoi de gros ensembles de données. Pensez-y comme passer-par-référence si vous êtes du monde C / C ++. Toutefois, contrairement à la référence passée, la version du contexte d'appel n'est plus disponible une fois transférée. Sa propriété est transférée dans le nouveau contexte. Par exemple, lors du transfert d'un ArrayBuffer de votre application principale vers un script de worker, l'original ArrayBuffer est effacé et n'est plus utilisable. Son contenu est (littéralement) transféré au contexte du worker.

/ Create a 32MB "file" and fill it.
var uInt8Array = new Uint8Array(1024 * 1024 * 32); // 32MB
for (var i = 0; i < uInt8Array.length; ++i) {
  uInt8Array[i] = i;
}

worker.postMessage(uInt8Array.buffer, [uInt8Array.buffer]);

Note : Pour plus d'informations sur les objets transférables, les performances et la détection des fonctionnalités pour cette méthode, lisez  Objets transférables: Lightning Fast! sur HTML5.

Workers intégrés

Il n'y a pas de manière «officielle» d'intégrer le code d'un worker dans une page Web, comme les éléments <script> pour les scripts normaux. Mais un élément <script> qui n'a pas d'attribut src et possède un attribut de type qui n'identifie pas un type MIME exécutable peut être considéré comme un élément de bloc de données que JavaScript pourrait utiliser. Les «blocs de données» sont une caractéristique plus générale de HTML5 qui peuvent contenir presque toutes les données textuelles. Ainsi, un worker pourrait être intégré de cette façon:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>MDN Example - Embedded worker</title>
<script type="text/js-worker">
  // Ce script NE SERA PAS analysé par les moteurs JS parce que son type MIME est texte/js-worker.
  var myVar = 'Hello World!';
  // Le reste de votre code worker va ici.
</script>
<script type="text/javascript">
  // Ce script SERA sera analysé par les moteurs JS  parce que leur type MIME est text/javascript.
  function pageLog(sMsg) {
    // Utiliser un fragment: le navigateur rendra/refoulera une seule fois.
    var oFragm = document.createDocumentFragment();
    oFragm.appendChild(document.createTextNode(sMsg));
    oFragm.appendChild(document.createElement('br'));
    document.querySelector('#logDisplay').appendChild(oFragm);
  }
</script>
<script type="text/js-worker">
  // Ce script NE SERA PAS analysé par les moteurs JS parce que son type MIME est texte/js-worker.
  onmessage = function(oEvent) {
    postMessage(myVar);
  };
  // Le reste de votre code worker vient ici.
</script>
<script type="text/javascript">
  // Ce script SERA analysé par les moteurs de JS parce que son type MIME est texte/javascript.

  // Autrefois...:
  // le constructeur de blob existait
  // ...mais maintenant nous utilisons blob...:
  var blob = new Blob(Array.prototype.map.call(document.querySelectorAll('script[type=\'text\/js-worker\']'), function (oScript) { return oScript.textContent; }),{type: 'text/javascript'});

  // Création d'une nouvelle propriété document.worker qui contient tout nos scripts "text/js-worker".
  document.worker = new Worker(window.URL.createObjectURL(blob));

  document.worker.onmessage = function(oEvent) {
    pageLog('Received: ' + oEvent.data);
  };

  // Démarrer le worker.
  window.onload = function() { document.worker.postMessage(''); };
</script>
</head>
<body><div id="logDisplay"></div></body>
</html>

Le worker incorporé est maintenant imbriqué dans une nouvelle propriété personnalisée de document.worker.

Il est aussi intéressant de noter que vous pouvez également convertir une fonction en un blob, puis générer une URL d'objet à partir de ce blob. Par exemple:.

function fn2workerURL(fn) {
  var blob = new Blob(['('+fn.toString()+')()'], {type: 'application/javascript'})
  return URL.createObjectURL(blob)
}

Autres exemples

Cette section fournit d'autres exemples de la façon d'utiliser les workers sur le Web.

Réalisation de calculs en arrière-plan

Les workers sont principalement utiles pour permettre à votre code d'effectuer des calculs intensifs pour le processeur sans bloquer le thread de l'interface utilisateur. Dans cet exemple, un worker est utilisé pour calculer les nombres de Fibonacci.

Le code JavaScript

Le code JavaScript suivant est stocké dans le fichier "fibonacci.js" référencé par le HTML dans la section suivante

var results = [];

function resultReceiver(event) {
  results.push(parseInt(event.data));
  if (results.length == 2) {
    postMessage(results[0] + results[1]);
  }
}

function errorReceiver(event) {
  throw event.data;
}

onmessage = function(event) {
  var n = parseInt(event.data);

  if (n == 0 || n == 1) {
    postMessage(n);
    return;
  }

  for (var i = 1; i <= 2; i++) {
    var worker = new Worker('fibonacci.js');
    worker.onmessage = resultReceiver;
    worker.onerror = errorReceiver;
    worker.postMessage(n - i);
  }
 };

Le worker définit la propriété sur l'envoi d'une fonction qui recevra les messages envoyés lorsque l'objet de travail postMessage () est appelé (notez que cela diffère de la définition d'une variable globale de ce nom ou d'une fonction avec ce nom. Var onmessage et function onmessage définiront les propriétés globales avec ces noms, mais ils n'inscriront pas la fonction pour recevoir les messages envoyés par la page Web qui a créé le travailleur). Cela entame la récurrence, engendrant de nouvelles copies de lui-même, pour gérer chaque itération du calcul.

Le code HTML

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8"  />
    <title>Test threads fibonacci</title>
  </head>
  <body>

  <div id="result"></div>

  <script language="javascript">

    var worker = new Worker('fibonacci.js');

    worker.onmessage = function(event) {
      document.getElementById('result').textContent = event.data;
      dump('Got: ' + event.data + '\n');
    };

    worker.onerror = function(error) {
      dump('Worker error: ' + error.message + '\n');
      throw error;
    };

    worker.postMessage('5');

  </script>
  </body>
</html>

La page Web crée un élément div avec le résultat ID, qui l'utilise pour afficher le résultat, puis génère le worker. Après avoir engendré le worker, le gestionnaire onmessage est configuré pour afficher les résultats en règlant le contenu de l'élément div et le gestionnaire Onerror est utilisé pour décharger le message d'erreur.

Enfin, un message est envoyé au worker pour le démarrer.

Essayez l'exemple.

Effectuer des E / S Web en arrière-plan

Vous pouvez en trouver un exemple dans cet article Utilisation des workers dans des extensions .

Division des tâches entre plusieurs workers

Au fur et à mesure que les ordinateurs "multi-core" deviennent de plus en plus courants, il est souvent utile de diviser des tâches complexes sur le plan informatique entre plusieurs workers, ce qui permet alors d'effectuer ces tâches sur des noyaux à processeurs multiples.

Autres types de worker

En plus des workers web dédiés et partagés, il existe d'autres types de workers disponibles:

  • ServiceWorkers servent essentiellement de serveurs proxy qui se trouvent entre les applications Web, le navigateur et le réseau (lorsqu'ils sont disponibles). Ils sont destinés (entre autres) à permettre la création d'expériences hors ligne efficaces, à intercepter les requêtes du réseau et à prendre des mesures appropriées en fonction de la disponibilité du réseau et des actifs actualisés sur le serveur. Ils permettront également l'accès aux notifications push et aux API de synchronisation de fond.
  • Chrome Workers sont un type de worker Firefox-only  que vous pouvez utiliser si vous développez des ajouts (addons) et si vous souhaitez utiliser des workers dans des extensions et avoir accès à js-ctypes dans votre worker. Voir ChromeWorker pour plus de  détails.
  • Audio Workers fournissent la possibilité d'effectuer un traitement audio par script dans un contexte de worker Web .

Fonctions et interfaces disponibles dans les workers

Vous pouvez utiliser la plupart des fonctionnalités JavaScript standard dans un worker Web, y compris :

La principale chose que vous ne pouvez pas faire dans un worker affecte directement la page parent. Cela comprend la manipulation du DOM et l'utilisation des objets de cette page. Vous devez le faire indirectement, en envoyant un message au script principal via DedicatedWorkerGlobalScope.postMessage, puis en agissant sur les changements à partir de là .

Note: pour une liste complète des fonctions disponibles des workers, voir Fonctions et interfaces disponibles des workers.

Spécifications

Specification Status Comment
HTML Living Standard
La définition de 'Web workers' dans cette spécification.
Standard évolutif  

Compatibilité des navigateurs

 
Fonctionnalité Chrome Firefox (Gecko) Internet Explorer Opera Safari (WebKit)
Basic support 4[1] 3.5 (1.9.1) 10.0 10.6[1] 4[2]
Shared workers 4[1] 29 (29) Pas de support 10.6 5
Pas de support 6.1[4]
Passing data using structured cloning 13 8 (8) 10.0 11.5 6
Passing data using transferable objects 17 webkit
21
18 (18) Pas de support 15 6
Global URL 10[3]
23
21 (21) 11 15 6[3]
Fonctionnalité Android Chrome for Android Firefox Mobile (Gecko) Firefox OS (Gecko) IE Phone Opera Mobile Safari Mobile
Basic support 4.4 4[1] 3.5 1.0.1 10.0 11.5[1] 5.1[2]
Shared workers Pas de support 4[1] 8 1.0.1 Pas de support Pas de support Pas de support
Passing data using structured cloning Pas de support 4 8 1.0.1 Pas de support Pas de support Pas de support
Passing data using transferable objects Pas de support Pas de support 18 1.0.1 Pas de support Pas de support Pas de support

[1] Chrome/Opéra donne une erreur "Uncaught SecurityError: Failed to construct 'Worker': Script at 'file:///Path/to/worker.js' cannot be accessed from origin 'null'.." lorsque vous essayez d'exécuter un worker local. Il doit être sur un domaine propre.

[2] Pour Safari 7.1.2, vous pouvez appeler console.log à partir d'un worker, mais il ne sera inactif. Les anciennes versions de Safari ne vous permettent pas d'appeler console.log à partir d'un worker.

[3] cette fonctionnalité est implémentée préfixée comme webkitURL.

[4] Safari support SharedWorker supprimé.

Voir aussi

 

Étiquettes et contributeurs liés au document

 Dernière mise à jour par : loella16,