Cette page a été traduite à partir de l'anglais par la communauté.Vous pouvez contribuer en rejoignant la communauté francophone sur MDN Web Docs.
Utilisation des web workers
LesWeb Workers sont un outil permettant au contenu web d'exécuter des scripts dans des tâches (threads) d'arrière-plan. Lethread associé auworker peut réaliser des tâches sans qu'il y ait d'interférence avec l'interface utilisateur. De plus, lesweb workers peuvent réaliser des opérations d'entrée/sortie grâce àXMLHttpRequest (bien que les attributsresponseXML etchannel soient nécessairement vides dans ces cas). Une fois créé, unworker peut envoyer des messages au code JavaScript qui l'a créé. De même, le script initial peut envoyer des messages auworker. Cette communication s'effectue grâce à des gestionnaires d'évènements. Dans cet article, nous verrons une introduction à l'utilisation desweb workers.
Dans cet article
- L'API Web Workers
- Lesworkers dédiés
- Lesworkers partagés
- Sûreté desthreads
- Règles de sécurité du contenu (content security policy, CSP)
- Échanger des données avec lesworkers : plus de détails
- Workers embarqués
- Autres exemples
- Autresworkers
- Fonctions et interfaces disponibles pour lesworkers
- Spécifications
- Voir aussi
L'API Web Workers
Unworker est un objet créé à l'aide d'un constructeur (par exempleWorker()) et qui exécute un fichier JavaScript donné. Ce fichier contient le code qui sera exécuté par lethread duworker. Lesworkers sont exécutés dans un contexte global qui n'est pas celui du document (généralementwindow). Aussi, si, dans unworker, on utilisewindow pour accéder à la portée globale (plutôt queself), cela provoquera une erreur.
Le contexte duworker est représenté par un objetDedicatedWorkerGlobalScope pour lesworkers dédiés et par un objetSharedWorkerGlobalScope sinon. Unworker dédié est uniquement accessible au travers du script qui l'a déclenché tandis qu'unworker partagé peut être utilisé par différents scripts.
Note :Voirla page d'entrée pour l'API Web Workers pour consulter la documentation de référence sur lesworkers et d'autres guides.
Il est possible d'exécuter n'importe quel code JavaScript dans lethread duworker, à l'exception des méthodes de manipulation du DOM ou de certaines propriétés et méthodes rattachées àwindow. On notera cependant qu'on peut tout à fait utiliser certaines API rendues disponibles viawindow comme lesWebSockets, les API de stockage de données telles queIndexedDB. Pour plus de détails, voirles fonctions et classes disponibles au sein desworkers.
Les données sont échangées entre lethread duworker et lethread principal par l'intermédiaire de messages. Chaque partie peut envoyer des messages à l'aide de la méthodepostMessage() et réagir aux messages reçus grâce au gestionnaire d'évènementonmessage (le message sera contenu dans l'attributdata de l'évènementmessage associé). Les données sont copiées dans le message, elles ne sont pas partagées.
Lesworkers peuvent également déclencher la création d'autresworkers tant que ceux-ci restent hébergés sur la même origine que la page parente. De plus, lesworkers pourront utiliserXMLHttpRequest pour effectuer des opérations réseau mais les attributsresponseXML etchannel deXMLHttpRequest renverront nécessairementnull.
Lesworkers dédiés
Comme indiqué plus haut, unworker dédié n'est accessible qu'au travers du script qui l'a initié. Dans cette section, nous étudierons le code JavaScript denotre exemple deworker dédié simple. Dans cet exemple, nous souhaitons multiplier deux nombres. Ces nombres sont envoyés à unworker dédié puis le résultat est renvoyé à la page et affiché.
Cet exemple est assez simple mais permet d'introduire les concepts de base autour desworkers. Nous verrons certains détails plus avancés dans la suite de cet article.
Détecter la possibilité d'utiliser lesworkers
Afin de gérer une meilleure amélioration progressive, une rétro-compatibilité et de présenter des messages d'erreur adéquats, il pourra être utile d'envelopper le code relatif auworker de la façon suivante (main.js) :
if (window.Worker) { ...}Initier unworker dédié
La création d'un nouveauworker est assez simple. On appellera le constructeurWorker() en indiquant l'URI du script à exécuter dans lethread associé auworker (main.js) :
var monWorker = new Worker("worker.js");Envoyer des messages auworker et y réagir
L'intérêt principal desworkers repose sur l'échange de messages à l'aide de la méthodepostMessage() et grâce au gestionnaire d'évènementonmessage. Lorsqu'on souhaite envoyer un message auworker, on enverra des messages de la façon suivante (main.js) :
premierNombre.onchange = function () { monWorker.postMessage([premierNombre.value, deuxiemeNombre.value]); console.log("Message envoyé au worker");};deuxiemeNombre.onchange = function () { monWorker.postMessage([premierNombre.value, deuxiemeNombre.value]); console.log("Message envoyé au worker");};Ici, nous disposons de deux éléments<input> représentés par les variablespremierNombre etdeuxiemeNombre. Lorsque l'un de ces deux champs est modifié, on utilisemonWorker.postMessage([premierNombre.value, deuxiemeNombre.value]) afin d'envoyer les deux valeurs auworker dans un tableau. Les messages peuvent être utilisés pour échanger n'importe quel type de valeur.
Dans leworker, on peut réagir au message reçu grâce à un gestionnaire d'évènement comme celui-ci (worker.js) :
onmessage = function (e) { console.log("Message reçu depuis le script principal."); var workerResult = "Résultat : " + e.data[0] * e.data[1]; console.log("Envoi du message de retour au script principal"); postMessage(workerResult);};Le gestionnaireonmessage permet d'exécuter du code lorsqu'un message est reçu. Le message même est disponible grâce à l'attributdata de l'évènement. Dans cet exemple, nous multiplions simplement les deux nombres avant d'utiliserpostMessage() à nouveau afin d'envoyer le résultat via un message destiné authread principal.
De retour dans lethread principal, nous pouvons utiliseronmessage à nouveau pour réagir à la réponse provenant duworker :
monWorker.onmessage = function (e) { resultat.textContent = e.data; console.log("Message reçu depuis le worker");};Ici, nous récupérons les données grâce à l'attributdata de l'évènement et nous mettons à jour le contenu du paragraphe avec l'attributtextContent de l'élément. Ainsi, l'utilisateur peut visualiser le résultat du calcul.
Note :On notera queonmessage etpostMessage() doivent être rattachés à un objetWorker lorsqu'ils sont utilisés depuis lethread principal (ici, c'étaitmonWorker) mais pas lorsqu'ils sont employés depuis leworker. En effet, dans leworker, c'est leworker qui constitue la portée globale et qui met à disposition ces méthodes.
Note :Lorsqu'un message est envoyé d'unthread à l'autre, ses données sont copiées. Elles ne sont pas partagées. Voirci-après pour plus d'explications à ce sujet.
Clôturer unworker
Si on doit arrêter unworker immédiatement, on pourra utiliser la méthodeterminate depuis lethread principal :
monWorker.terminate();Lorsque cette méthode exécuté, lethread associé auworker est tué immédiatement.
Gérer les erreurs
Lorsqu'une erreur d'exécution se produit avec leworker, son gestionnaire d'évènementonerror est appelé et reçoit un évènementerror qui implémente l'interfaceErrorEvent.
Cet évènement ne bouillonne (bubble) pas et peut être annulé. Pour empêcher les conséquences par défaut, on pourra utiliser la méthodepreventDefault() rattachée à l'évènement d'erreur.
L'évènement décrivant l'erreur possède notamment trois propriétés intéressantes :
Initier desworkers fils
Lesworkers peuvent également engendrer d'autresworkers. Cesworkers-fils doivent être hébergés sur la même origine que la page initiale. De plus, les URI des workers-fils sont résolues relativement à l'emplacement duworker père (plutôt que par rapport à la page parente). Ces contraintes permettent de simplifier le suivi des dépendances.
Importer des scripts et des bibliothèques
Lesthreads d'exécution desworkers peuvent accéder à la fonction globaleimportScripts(), qui leur permet d'importer des scripts. Cette fonction prend zéro à plusieurs URL en paramètres et importe les ressources associées. Voici quelques exemples valides :
importScripts(); /* n'importe rien */importScripts("toto.js"); /* importe uniquement "toto.js" */importScripts("toto.js", "truc.js"); /* importe deux scripts */importScripts( "//example.com/hello.js",); /* importe un script d'une autre origine */Lors d'un import, le navigateur chargera chacun des scripts puis l'exécutera. Chaque script pourra ainsi mettre à disposition des objets globaux qui pourront être utilisés par leworker. Si le script ne peut pas être chargé, une exceptionNETWORK_ERROR sera levée et le code assicé ne sera pas exécuté. Le code exécuté précédemment (y compris celui-ci reporté à l'aide dewindow.setTimeout()) continuera cependant d'être fonctionnel. Les déclarations de fonction situéesaprèsimportScripts() sont également exécutées car é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 des arguments passés àimportScripts() . Cet exécution est effectuée de façon synchrone etimportScripts() ne rendra pas la main tant que l'ensemble des scripts n'auront pas été chargés et exécutés.
Lesworkers partagés
Unworker partagé est accessible par plusieurs scripts (même si ceux-ci proviennent de différentes fenêtres,iframes voire d'autresworkers). Dans cette section, nous illustrerons les concepts à l'aide del'exemple simple d'unworker partagé. Cet exemple est semblable à l'exemple utilisé pour leworker dédié. Il diffère car il possède deux fonctions, gérées par deux fichiers de script distincts : une fonction permettant de multiplier deux nombres et une fonction permettant d'élever un nombre au carré. Les deux scripts utilisent le mêmeworker pour réaliser le calcul demandé.
Ici, nous nous intéresserons particulièrement aux différences entre lesworkers dédiés et lesworkers partagés. Dans cet exemple, nous aurons deux pages HTML, chacune utilisant du code JavaScript employant le mêmeworker.
Note :Si on peut accéder à unworker partagé depuis différents contextes de navigations, ces contextes de navigation doivent néanmoins partager la même origine (même protocole, même hôte, même port).
Note :Dans Firefox, lesworkers partagés ne peuvent pas être partagés entre les documents chargés en navigation privée et les documents chargés en navigation classique (bug Firefox 1177621).
Initier unworker partagé
La création d'un nouveauworker partagé est assez semblable à la création d'unworker dédié. On utilise alors un constructeur différent :
var monWorker = new SharedWorker("worker.js");Une différence fondamentale avec lesworkers dédiés est l'utilisation d'un objetport pour la communication. Un port sera explicitement ouvert pour être utilisé afin de communiquer avec leworker (dans le cas desworkers dédiés, ce port est ouvert implicitement).
La connexion au port doit être démarrée implicitement avec l'utilisation du gestionnaire d'évènementonmessage ou explicitement avec la méthodestart() avant qu'un message soit envoyé. On utilisera uniquementstart() si l'évènementmessage est détecté avec la méthodeaddEventListener().
Note :Lorsqu'on utilise la méthodestart() afin d'ouvrir le port de connexion, celle-ci doit être appelée de part et d'autre (depuis lethread parentet depuis leworker) si on souhaite disposer d'une connexion bidirectionnelle.
Échanger des messages avec unworker partagé et y réagir
On peut alors envoyer des messages auworker. Dans le cas d'unworker partagé, la méthodepostMessage() doit être appelée via l'objetport (là aussi, vous pouvez étudier le code demultiply.js etsquare.js) :
carreNombre.onchange = function () { monWorker.port.postMessage([carreNombre.value, carreNombre.value]); console.log("Message envoyé au worker");};Du côté duworker, les choses sont également légèrement plus compliquées (voirworker.js) :
onconnect = function (e) { var port = e.ports[0]; port.onmessage = function (e) { var workerResult = "Résultat : " + e.data[0] * e.data[1]; port.postMessage(workerResult); };};On commence par utiliser le gestionnaireonconnect afin de déclencher du code à la connexion au port (c'est-à-dire lorsque le gestionnaireonmessage est déclaré depuis lethread parent ou lorsque la méthodestart() est invoquée explicitement depuis lethread parent).
On utilise l'attributports de l'évènement afin de récupérer le port utilisé et on le place dans une variable.
Ensuite, sur ce port, on ajoute un gestionnaire d'évènement pour l'évènementmessage afin de faire les calculs et de renvoyer le résultat authread principal. En définissant ce gestionnaire pourmessage dans lethread duworker, on ouvre implicitement le port pour la connexion authread parent : il n'est donc pas nécessaire d'invoquerport.start().
Enfin, dans le script de la page, on gère le message du résultat (voirmultiply.js etsquare.js):
monWorker.port.onmessage = function (e) { result2.textContent = e.data; console.log("Message reçu depuis le worker");};Lorsqu'un message provient du port associé auworker, on vérifie son type puis on insère le résultat dans le paragraphe associé.
Sûreté desthreads
L'interfaceWorker engendre desthreads au sens du système d'exploitation. Certains développeurs avertis pourront se demander si cette communication à base dethreads ne peut pas générer d'effets indésirables tels que des situations de compétition (race conditions).
Toutefois, la communication entre lesweb workers est contrôlée explicitement dans les scripts et il n'y a pas d'accès aux composants ou au DOM qui ne seraient pas sûrs à ce niveau. De plus, la communication entre lesthreads s'effectue par recopie de données. Aussi, s'il n'est théoriquement pas impossible de ne pas avoir de tels problèmes, il faudrait les chercher pour les provoquer.
Règles de sécurité du contenu (content security policy, CSP)
Lesworkers disposent de leur propre contexte d'exécution, distinct de celui du document qui les a créés. Aussi, en général, lesworkers ne sont pas gérés par lapolitique de sécurité de contenu du document (ou duworker parent) responsable de leur création. Ainsi, si un document est servi avec l'en-tête suivant :
Content-Security-Policy: script-src 'self'
Cette règle empêchera n'importe quel script inclus dans le document d'utilisereval(). Toutefois, si le script génère unworker, le code exécuté par ceworker pourra utilisereval().
Pour appliquer une règle de sécurité auworker, il faudra fournir un en-têteContent-Security-Policy approprié pour la requête responsable du service du script duworker.
Si l'origine du script duworker est un identifiant global unique (si son URL utilise le schémadata:// oublob:// par exemple), leworker héritera du CSP associé au document responsable de sa création.
Échanger des données avec lesworkers : plus de détails
Les données échangées entre le document principal et lesworkers sontcopiées et non partagées. Lorsqu'ils sont envoyés auworker, les objets sont sérialisés (puis désérialisés à leur réception). La page et leworkerne partagent pas le même exemplaire et on a donc deux versions d'une part et d'autre. La plupart des navigateurs implémentent cette approche avecune clonage structurel.
Pour illustrer ce point, on prendra une fonction intituléeemulateMessage() et qui simule le comportement d'une valeur clonée (pas partagée) avec unworker attaché à la page principale et réciproquement :
function emulateMessage(vVal) { return eval("(" + JSON.stringify(vVal) + ")");}// Tests// test #1var example1 = new Number(3);console.log(typeof example1); // objectconsole.log(typeof emulateMessage(example1)); // number// test #2var example2 = true;console.log(typeof example2); // booleanconsole.log(typeof emulateMessage(example2)); // boolean// test #3var example3 = new String("Hello World");console.log(typeof example3); // objectconsole.log(typeof emulateMessage(example3)); // string// test #4var example4 = { name: "John Smith", age: 43,};console.log(typeof example4); // objectconsole.log(typeof emulateMessage(example4)); // object// test #5function Animal(sType, nAge) { this.type = sType; this.age = nAge;}var example5 = new Animal("Cat", 3);console.log(example5.constructor); // Animalconsole.log(emulateMessage(example5).constructor); // ObjectUne valeur qui est clonée et non partagée est appelée un message. Les messages peuvent être envoyés et reçus grâce àpostMessage() et au gestionnaire d'évènement pourmessage (dont l'attributdata contiendra les données copiées).
example.html (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 code duworker) :
postMessage("I'm working before postMessage('ali').");onmessage = function (oEvent) { postMessage("Hi " + oEvent.data);};L'algorithme de clonage structurel permet de sérialiser aussi bien des données JSON que d'autres formats et permet notamment de gérer les références circulaires (ce que JSON ne permet pas de gérer nativement).
Les objets transférables - échanger des données avec transfert de la propriété
Chrome 17+ et Firefox 18+ permettent également d'échanger certains types d'objet (qualifiés de transférables et qui implémentent l'interfaceTransferable) avec desworkers et à haute performance. Les objets transférables sont passés d'un contexte à l'autre avec une opérationzero-copy qui permet d'obtenir des améliorations sensibles lors de l'échange de données volumineuses. On peut voir cela comme un passage de référence du monde C/C++ mais les données qui sont transférées depuis le contexte appelant ne sont plus disponibles dans ce contexte après le transfert. La propriété des données est transférée au nouveau contexte. Ainsi, lorsqu'on transfère un objetArrayBuffer depuis l'application principale vers leworker, l'objetArrayBuffer de départ est nettoyé et ne peut plus être utilisé, son contenu est (littéralement) transféré au contexte duworker.
// Créer un fichier de 32MB et le remplir.var uInt8Array = new Uint8Array(1024 * 1024 * 32); // 32MBfor (var i = 0; i < uInt8Array.length; ++i) { uInt8Array[i] = i;}worker.postMessage(uInt8Array.buffer, [uInt8Array.buffer]);Note :Pour plus d'informations quant aux objets transférables, aux performances associées et à la détection de ces fonctionnalités, on pourra lireTransferable Objects: Lightning Fast.
Workers embarqués
Il n'existe pas de méthode standard pour embarquer le code d'un worker dans une page web à la façon des éléments<script>. Toutefois, un élément<script>, qui ne possède pas d'attributsrc, qui possède un attributtype ne correspondant pas à un type MIME exécutable pourra être considéré comme un bloc de données pouvant être utilisé par JavaScript. Ces blocs de données sont une fonctionnalité HTML5 qui permet de transporter n'importe quelle donnée textuelle. On pourrait donc embarquer unworker de cette façon :
<!doctype html><html> <head> <meta charset="UTF-8" /> <title>Exemple MDN - Worker embarqué</title> <script type="text/js-worker"> // Ce script ne sera pas analysé par le moteur JS car // son type MIME est text/js-worker. var maVar = 'Coucou monde !'; // Reste du code du worker. </script> <script type="text/javascript"> // Ce script sera analysé par le moteur JS car son type MIME // est text/javascript. function pageLog(sMsg) { // On utilise un fragment afin que le navigateur ne rende/peigne // qu'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 le moteur JS car son type // MIME est text/js-worker. onmessage = function(oEvent) { postMessage(myVar); }; // Reste du code du worker </script> <script type="text/javascript"> // Ce script sera analysé par le moteur JS car son type MIME est // text/javascript var blob = new Blob( Array.prototype.map.call( document.querySelectorAll("script[type='text\/js-worker']"), function (oScript) { return oScript.textContent; }, ), { type: "text/javascript" }, ); // On crée une nouvelle propriété document.worker qui contient // tous les scripts "text/js-worker". document.worker = new Worker(window.URL.createObjectURL(blob)); document.worker.onmessage = function (oEvent) { pageLog("Received: " + oEvent.data); }; // On démarre le worker. window.onload = function () { document.worker.postMessage(""); }; </script> </head> <body> <div></div> </body></html>Leworker embarqué est désormais injecté dans la propriétédocument.worker.
On notera également qu'on peut convertir une fonction en unBlob et générer une URL d'objet vers ce blob. Par exemple :
function fn2workerURL(fn) { var blob = new Blob(["(" + fn.toString() + ")()"], { type: "application/javascript", }); return URL.createObjectURL(blob);}Autres exemples
Dans cette section nous voyons d'autres exemples d'application.
Effectuer des calculs en arrière-plan
Lesworkers sont notamment utiles pour réaliser des opérations de traitement intensives sans bloquer pour autant lethread responsable de l'interface utilisateur. Dans cet exemple, on utilise unworker afin de calculer la suite de Fibonacci.
JavaScript
Le code JavaScript suivant sera enregistré dans le fichier "fibonacci.js" auquel on fera référence dans le document HTML ci-après.
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); }};On a défini la propriétéonmessage avec une fonction qui recevra les messages envoyés auworker viapostMessage(). On initie alors la récursion et on déclenche des copies duworker afin de gérer chacune des itérations liées au calcul.
HTML
<!doctype html><html> <head> <meta charset="UTF-8" /> <title>Test threads fibonacci</title> </head> <body> <div></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) { console.error("Worker error: " + error.message + "\n"); throw error; }; worker.postMessage("5"); </script> </body></html>Dans la page web, on crée un élémentdiv avec l'identifiantresult. C'est cet élément qui sera utilisé afin d'afficher le résultat.
Ensuite, on lance leworker. Après avoir initié leworker, on configure le gestionnaire d'évènementonmessage afin d'afficher le résultat via lediv. On configure également le gestionnaireonerror afin d'afficher l'erreur de la console.
Enfin, on envoie un message auworker afin de le démarrer.
Répartir des tâches entre plusieursworkers
Les ordinateurs dotés de plusieurs coeurs se généralisent et il peut s'avérer utile de fragmenter une tâche complexe entre différentsworkers afin de tirer parti des différents coeurs du processeur.
Autresworkers
En plus des webworkers (dédiés et partagés), il existe d'autres types deworkers :
- Les serviceworkers peuvent notamment servir de serveurs mandataires (proxy) entre les applications web, le navigateur et le réseau (lorsque celui-ci est disponible). Cesworkers sont conçus afin de permettre des utilisations hors-ligne en interceptant les requêtes réseau et en déclenchant les actions nécessaires selon que le réseau est disponible ou non et que les ressources souhaitées sont disponibles sur le serveur. Cesworkers permettent de déclencher des notificationspush et d'utiliser des API de synchronisation en arrière-plan.
- Lesworklets audio permettent de traiter des signaux audios en arrière-plan (fonctionnalité expérimentale).
Fonctions et interfaces disponibles pour lesworkers
La plupart des fonctionnalités JavaScript standard peuvent être utilisées dans lesweb workers, dont :
En revanche, unworker ne pourra pas directement manipuler la page parente et notamment le DOM et les objets de la page. Il faudra effectuer ce traitement indirectement, via des messages.
Note :Pour avoir une liste exhaustive des fonctionnalités disponibles pour lesworkers, voirles fonctions et interfaces disponibles pour lesworkers.
Spécifications
| Specification |
|---|
| HTML> # workers> |