AngularJS et web workers

Comment angularJS peut-il utiliser les opérateurs Web pour exécuter des processus en arrière-plan? Y at-il un modèle que je devrais suivre pour ce faire?

Actuellement, j'utilise un service qui possède le modèle dans un travailleur Web distinct. Ce service met en œuvre des méthodes comme:

ClientsFacade.calculateDebt(client1); //Just an example.. 

Dans la mise en œuvre, cette méthode envoie un message au travailleur avec les données. Cela me permet de résumer le fait qu'il se déroule dans un thread distinct et je peux également fournir une implémentation qui interroge un serveur ou même celui qui effectue cette action dans le même thread.

Puisque je suis nouveau sur javascript et que je recycle les connaissances que j'ai d'autres plates-formes, je me demande si c'est quelque chose que vous feriez ou peut-être Angular, c'est ce que j'utilise, et je propose une sorte de façon. En outre, cela introduit une modification de mon architecture puisque le travailleur doit pousser explicitement les modifications au contrôleur, qui met ensuite à jour ses valeurs, puis cela se reflète dans la vue, est-ce que je suis en train d'englober cela? C'est un peu frustrant que les travailleurs sur le Web «me protègent tant» de se coincer en ne me permettant pas de partager la mémoire, etc.

La communication avec les opérateurs Web se fait par un mécanisme de messagerie. L'interception de ces messages se produit dans un rappel. Dans AngularJS, le meilleur emplacement pour mettre un travailleur sur le Web est dans un service tel que vous l'avez noté. La meilleure façon de faire face à cela est d'utiliser des promesses, dont Angular travaille incroyablement avec.

Voici un exemple de webworker dans un service

 var app = angular.module("myApp",[]); app.factory("HelloWorldService",['$q',function($q){ var worker = new Worker('doWork.js'); var defer = $q.defer(); worker.addEventListener('message', function(e) { console.log('Worker said: ', e.data); defer.resolve(e.data); }, false); return { doWork : function(myData){ defer = $q.defer(); worker.postMessage(myData); // Send data to our worker. return defer.promise; } }; }); 

Maintenant, quelle que soit l'entité externe qui accède au service Hello World, il ne faut pas se soucier des détails d'implémentation de HelloWorldService . HelloWorldService pourrait probablement traiter les données sur un web worker , sur http ou faire le traitement là-bas.

J'espère que cela a du sens.

Une question très intéressante! Je trouve la spécification du travailleur Web un peu maladroite (probablement pour de bonnes raisons, mais toujours gênante). La nécessité de garder le code du travailleur dans un fichier distinct rend l'intention d'un service difficile à lire et présente des dépendances aux URL de fichiers statiques dans votre code de demande angulaire. Ce problème peut être atténué en utilisant l'URL.createObjectUrl () qui peut être utilisé pour créer une URL pour une chaîne JavaScript. Cela nous permet de spécifier le code du travailleur dans le même fichier qui crée le travailleur.

 var blobURL = URL.createObjectURL(new Blob([ "var i = 0;//web worker body" ], { type: 'application/javascript' })); var worker = new Worker(blobURL); 

La spécification du travailleur Web maintient également les contextes du travailleur et du thread principal complètement séparés pour éviter que les situations ne soient des blocages et des mises en veille, etc. Mais cela signifie également que vous n'aurez pas accès à vos services angulaires chez le travailleur sans avoir du violon. Le travailleur manque de certaines des choses que nous (et angulaires) attendons lors de l'exécution de JavaScript dans le navigateur, comme la variable globale «document», etc. En «moquant» ces fonctionnalités de navigateur requises dans le travailleur, nous pouvons être angulaires à exécuter.

 var window = self; self.history = {}; var document = { readyState: 'complete', cookie: '', querySelector: function () {}, createElement: function () { return { pathname: '', setAttribute: function () {} }; } }; 

Certaines fonctionnalités ne fonctionneront évidemment pas, les liaisons avec les DOM, etc. Mais le cadre d'injection et, par exemple, le service $ http fonctionnera très bien, ce qui est probablement ce que nous voulons chez un travailleur. Ce que nous obtenons par là, c'est que nous pouvons exécuter des services angulaires standard dans un travailleur. Nous pouvons donc tester les services utilisés dans le travailleur comme nous le ferions avec toute autre dépendance angulaire.

J'ai fait un article qui en développe un peu plus ici et crée un compte Github qui crée un service qui met en œuvre les idées discutées ci-dessus ici

J'ai trouvé un exemple pleinement fonctionnel des travailleurs sur le Web chez Angular ici

 webworker.controller('webWorkerCtrl', ['$scope', '$q', function($scope, $q) { $scope.workerReplyUI; $scope.callWebWorker = function() { var worker = new Worker('worker.js'); var defer = $q.defer(); worker.onmessage = function(e) { defer.resolve(e.data); worker.terminate(); }; worker.postMessage("http://jsonplaceholder.typicode.com/users"); return defer.promise; } $scope.callWebWorker().then(function(workerReply) { $scope.workerReplyUI = workerReply; }); }]); 

Il utilise des promesses d'attendre que le travailleur renvoie le résultat.

Travailleur Web angulaire avec exemple de sondage

Lorsque vous traitez avec les travailleurs d'AngularJS, il est souvent nécessaire que votre script de travail soit en ligne (si vous utilisez des outils de construction comme gulp / grunt) et nous pouvons le faire en utilisant l'approche suivante.

L'exemple ci-dessous montre également comment le sondage peut être effectué sur le serveur en utilisant les travailleurs:

D'abord, créez notre usine de travail:

  module.factory("myWorker", function($q) { var worker = undefined; return { startWork: function(postData) { var defer = $q.defer(); if (worker) { worker.terminate(); } // function to be your worker function workerFunction() { var self = this; self.onmessage = function(event) { var timeoutPromise = undefined; var dataUrl = event.data.dataUrl; var pollingInterval = event.data.pollingInterval; if (dataUrl) { if (timeoutPromise) { setTimeout.cancel(timeoutPromise); // cancelling previous promises } console.log('Notifications - Data URL: ' + dataUrl); //get Notification count var delay = 5000; // poller 5sec delay (function pollerFunc() { timeoutPromise = setTimeout(function() { var xmlhttp = new XMLHttpRequest(); xmlhttp.onreadystatechange = function() { if (xmlhttp.readyState == 4 && xmlhttp.status == 200) { var response = JSON.parse(xmlhttp.responseText); self.postMessage(response.id); pollerFunc(); } }; xmlhttp.open('GET', dataUrl, true); xmlhttp.send(); }, delay); })(); } } } // end worker function var dataObj = '(' + workerFunction + ')();'; // here is the trick to convert the above fucntion to string var blob = new Blob([dataObj.replace('"use strict";', '')]); // firefox adds user strict to any function which was blocking might block worker execution so knock it off var blobURL = (window.URL ? URL : webkitURL).createObjectURL(blob, { type: 'application/javascript; charset=utf-8' }); worker = new Worker(blobURL); worker.onmessage = function(e) { console.log('Worker said: ', e.data); defer.notify(e.data); }; worker.postMessage(postData); // Send data to our worker. return defer.promise; }, stopWork: function() { if (worker) { worker.terminate(); } } } }); 

Ensuite, à partir de notre contrôleur, appelez l'usine de travail:

 var inputToWorker = { dataUrl: "http://jsonplaceholder.typicode.com/posts/1", // url to poll pollingInterval: 5 // interval }; myWorker.startWork(inputToWorker).then(function(response) { // complete }, function(error) { // error }, function(response) { // notify (here you receive intermittent responses from worker) console.log("Notification worker RESPONSE: " + response); }); 

Vous pouvez appeler myWorker.stopWork(); À tout moment pour terminer le travailleur de votre contrôleur!

Ceci est testé dans IE11 + et FF et Chrome

Vous pouvez également regarder le plugin angulaire https://github.com/vkiryukhin/ng-vkthread

Qui vous permet d'exécuter une fonction dans un thread distinct. Utilisation de base:

 /* function to execute in a thread */ function foo(n, m){ return n + m; } /* create an object, which you pass to vkThread as an argument*/ var param = { fn: foo // <-- function to execute args: [1, 2] // <-- arguments for this function }; /* run thread */ vkThread.exec(param).then( function (data) { console.log(data); // <-- thread returns 3 } ); 

Exemples et document API: http://www.eslinstructor.net/ng-vkthread/demo/

– Vadim