javascript - AngularJS y trabajadores de la web
web-worker (5)
Trabajador web angular con ejemplo de sondeo
Cuando trabajas con los trabajadores en AngularJS, a menudo se requiere que tu script de trabajo esté en línea (en caso de que estés usando algunas herramientas de compilación como gulp / grunt) y podemos lograrlo usando el siguiente enfoque.
El siguiente ejemplo también muestra cómo se puede realizar el sondeo al servidor que usa trabajadores:
Primero permitamos crear nuestra fábrica de trabajadores:
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();
}
}
}
});
A continuación, desde nuestro controlador, llame a la fábrica de trabajadores:
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);
});
Puede llamar a myWorker.stopWork();
¡en cualquier momento para despedir al trabajador de su controlador!
Esto se prueba en IE11 + y FF y Chrome
¿Cómo puede angularJS usar web workers para ejecutar procesos en segundo plano? ¿Hay algún patrón que deba seguir al hacer esto?
Actualmente, estoy usando un servicio que tiene el modelo en un trabajador web separado. Este servicio implementa métodos como:
ClientsFacade.calculateDebt(client1); //Just an example..
En la implementación, este método envía un mensaje al trabajador con los datos. Esto me permite abstraer el hecho de que se está realizando en un subproceso separado y también podría proporcionar una implementación que consulta contra un servidor o incluso uno que hace esta acción en el mismo subproceso.
Como soy nuevo en JavaScript y solo estoy reciclando el conocimiento que tengo de otras plataformas, me pregunto si esto es algo que harías o quizás Angular, que es lo que estoy usando, ofrece una forma de hacerlo. También esto introduce un cambio en mi arquitectura ya que el trabajador debe impulsar cambios de manera explícita al controlador, que luego actualiza sus valores y luego esto se refleja en la vista, ¿estoy sobre ingeniería? Es un poco frustrante que los trabajadores de la web me "protejan" tanto para no arruinarme al no poder compartir mi memoria, etc.
Encontré un ejemplo completamente funcional de trabajadores web en Angular here
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;
});
}]);
Utiliza promesas de esperar a que el trabajador devuelva el resultado.
La comunicación con los trabajadores de la Web ocurre a través de un mecanismo de mensajería. La interceptación de estos mensajes ocurre en una devolución de llamada. En AngularJS, la mejor ubicación para poner a un trabajador de la web es en un servicio como usted señaló debidamente. La mejor forma de lidiar con esto es usar promesas, con lo que Angular funciona increíblemente.
Aquí hay un ejemplo de un webworker
en 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;
}
};
});
Ahora cualquier entidad externa que acceda al servicio Hello World no necesita preocuparse por los detalles de implementación de HelloWorldService
. HelloWorldService
probablemente podría procesar los datos a través de un web worker
, a través de http
o hacer el procesamiento allí mismo.
Espero que esto tenga sentido.
También puedes echarle un vistazo al complemento angular https://github.com/vkiryukhin/ng-vkthread
que le permite ejecutar una función en un hilo separado. uso básico:
/* 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
}
);
Ejemplos y API doc: http://www.eslinstructor.net/ng-vkthread/demo/
--Vadim
Una pregunta muy interesante! Encuentro que la especificación del trabajador web es un poco incómoda (probablemente por buenas razones, pero aún incómoda). La necesidad de mantener el código del trabajador en un archivo separado hace que la intención de un servicio sea difícil de leer e introduce dependencias a las URL de archivos estáticos en su código de aplicación angular. Este problema puede mitigarse utilizando el URL.createObjectUrl () que se puede usar para crear una URL para una cadena de JavaScript. Esto nos permite especificar el código de trabajador en el mismo archivo que crea el trabajador.
var blobURL = URL.createObjectURL(new Blob([
"var i = 0;//web worker body"
], { type: ''application/javascript'' }));
var worker = new Worker(blobURL);
La especificación del trabajador web también mantiene los contextos del trabajador y del hilo principal completamente separados para evitar situaciones en las que se puedan producir bloqueos y bloqueos automáticos, etc. Pero también significa que no tendrá acceso a sus servicios angulares en el trabajador sin algún toque de violín. El trabajador carece de algunas de las cosas que (y angulares) esperamos al ejecutar JavaScript en el navegador, como la variable global "documento", etc. Al "burlarse" de estas funciones requeridas del navegador en el trabajador podemos hacer que angular se ejecute.
var window = self;
self.history = {};
var document = {
readyState: ''complete'',
cookie: '''',
querySelector: function () {},
createElement: function () {
return {
pathname: '''',
setAttribute: function () {}
};
}
};
Algunas funciones obviamente no funcionarán, enlaces al DOM, etc. Pero el marco de inyección y, por ejemplo, el servicio $ http funcionarán bien, que es probablemente lo que queremos en un trabajador. Lo que ganamos con esto es que podemos ejecutar servicios angulares estándar en un trabajador. Por lo tanto, podemos probar los servicios utilizados en el trabajador de la misma manera que lo haríamos con cualquier otra dependencia angular.
Hice una publicación que elabora un poco más sobre esto here y creó un repositorio github que crea un servicio que implementa las ideas discutidas anteriormente here