AngularJS und Web Worker

74

Wie kann angleJS Webworker verwenden, um Prozesse im Hintergrund auszuführen? Gibt es ein Muster, dem ich dabei folgen sollte?

Derzeit verwende ich einen Dienst, der das Modell in einem separaten Web-Worker hat. Dieser Service implementiert Methoden wie:

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

In der Implementierung sendet diese Methode eine Nachricht mit den Daten an den Worker. Auf diese Weise kann ich die Tatsache abstrahieren, dass es in einem separaten Thread ausgeführt wird, und ich könnte auch eine Implementierung bereitstellen, die Abfragen an einen Server oder sogar eine, die diese Aktion im selben Thread ausführt.

Da ich neu in Javascript bin und nur das Wissen wiederverwerte, das ich von anderen Plattformen habe, frage ich mich, ob Sie dies tun würden oder ob Angular, das ich verwende, eine Art Möglichkeit bietet, dies zu tun. Dies führt auch zu einer Änderung meiner Architektur, da der Worker Änderungen explizit an den Controller senden muss, der dann seine Werte aktualisiert. Dies spiegelt sich dann in der Ansicht wider. Bin ich über das Engineering hinaus? Es ist ein bisschen frustrierend, dass Web-Worker mich so sehr vor Misserfolgen "schützen", indem sie mir nicht erlauben, Speicher usw. zu teilen.

arg20
quelle

Antworten:

98

Die Kommunikation mit Web-Mitarbeitern erfolgt über einen Messaging-Mechanismus. Das Abfangen dieser Nachrichten erfolgt in einem Rückruf. In AngularJS ist der beste Ort, an dem ein Web-Worker eingesetzt werden kann, ein Dienst, wie Sie ordnungsgemäß angegeben haben. Der beste Weg, um damit umzugehen, besteht darin, Versprechen zu verwenden, mit denen Angular erstaunlich gut zusammenarbeitet.

Hier ist ein Beispiel für a webworkerin aservice

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

});

Nun muss sich jede externe Entität, die auf den Hello World-Dienst zugreift, nicht um die Implementierungsdetails von kümmern HelloWorldService- HelloWorldServicekönnte die Daten wahrscheinlich über a web worker, over verarbeiten httpoder die Verarbeitung genau dort durchführen.

Hoffe das macht Sinn.

Ganaraj
quelle
5
Was ist doWork.js in var worker = new Worker ('doWork.js')? ?
Acudars
2
Es ist ein Verweis auf die externe js-Datei, die den Code für den Web-Worker enthält.
Ganaraj
3
Ja, ist es möglich, Dienste wie $ http in doWork.js zu verwenden?
iLemming
9
Wird doWorkes erneut aufgerufen, bevor der Worker fertig ist, wird es nicht deferüberschrieben? Das zweite Versprechen würde sich dann mit dem ersten Ergebnis auflösen, und das erste Versprechen würde sich niemals auflösen.
Hughes
1
@hughes Nach dem Lesen des Codes denke ich, dass Sie Recht haben. Dieser Code muss geändert werden, um bei jedem Aufruf von doWork (Daten) eine neue Verzögerung bereitzustellen, die aufgelöst werden kann.
Ryanm
16

Eine sehr interessante Frage! Ich finde die Web-Worker-Spezifikation etwas umständlich (wahrscheinlich aus guten Gründen, aber immer noch umständlich). Die Notwendigkeit, den Worker-Code in einer separaten Datei zu speichern, erschwert das Lesen der Absicht eines Dienstes und führt Abhängigkeiten zu statischen Datei-URLs in Ihrem eckigen Anwendungscode ein. Dieses Problem kann mithilfe der URL.createObjectUrl () behoben werden, mit der eine URL für eine JavaScript-Zeichenfolge erstellt werden kann. Auf diese Weise können wir den Worker-Code in derselben Datei angeben, in der der Worker erstellt wird.

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

Die Web-Worker-Spezifikation hält auch die Worker- und Haupt-Thread-Kontexte vollständig getrennt, um Situationen zu vermeiden, in denen Deadlocks und Livelocks usw. auftreten können. Es bedeutet aber auch, dass Sie im Arbeiter keinen Zugriff auf Ihre eckigen Dienste haben, ohne etwas herumzuspielen. Dem Worker fehlen einige der Dinge, die wir (und eckig) erwarten, wenn wir JavaScript im Browser ausführen, wie die globale Variable "document" usw. Durch "Verspotten" dieser erforderlichen Browserfunktionen im Worker können wir eckig ausführen.

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

Einige Funktionen funktionieren offensichtlich nicht, Bindungen an das DOM usw. Aber das Injection-Framework und zum Beispiel der $ http-Dienst funktionieren einwandfrei, was wir wahrscheinlich von einem Worker erwarten. Was wir dadurch gewinnen, ist, dass wir in einem Arbeiter Standard-Winkeldienste ausführen können. Wir können daher die im Worker verwendeten Dienste wie bei jeder anderen Winkelabhängigkeit einem Unit-Test unterziehen.

Ich einen Beitrag , die ein bisschen mehr über diese erarbeitet hier und ein GitHub Repo geschaffen , die einen Dienst erstellt , die implementiert die Ideen oben diskutierten hier

liket
quelle
Denken Sie für Angular> = v1.5.7 daran, self.Node = {prototype: []} hinzuzufügen. siehe github.com/FredrikSandell/angular-workers/issues/15
Adavo
11

Ich habe hier in Angular ein voll funktionsfähiges Beispiel für Web-Worker gefunden

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

}]);

Es verwendet Versprechen, um darauf zu warten, dass der Arbeiter das Ergebnis zurückgibt.

Oktan
quelle
3
Die Site, auf der das Beispiel gehostet wird, ist nicht mehr verfügbar. Hier ist der Archivlink web.archive.org/web/20150709233911/http://…
Michael Khalili
8

Angular Web Worker mit Abfragebeispiel

Wenn Sie mit den Workern in AngularJS zu tun haben, muss Ihr Worker-Skript häufig inline sein (falls Sie einige Build-Tools wie gulp / grunt verwenden). Dies können wir mit dem folgenden Ansatz erreichen.

Das folgende Beispiel zeigt auch, wie mit Arbeitern Abfragen auf dem Server durchgeführt werden können:

Lassen Sie uns zuerst unsere Arbeiterfabrik erstellen:

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

Rufen Sie als nächstes von unserer Steuerung aus die Arbeiterfabrik an:

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

Sie können myWorker.stopWork();jederzeit anrufen, um den Worker von Ihrem Controller aus zu beenden!

Dies wird in IE11 + und FF und Chrome getestet

Chanakya Vadla
quelle
@ChanuSukamo dies wird nicht aufgerufen. Und Sie haben das PollingInterval verpasst, das nirgendwo verwendet wird.
IamStalker
2

Sie können sich auch das Winkel-Plugin https://github.com/vkiryukhin/ng-vkthread ansehen

Hiermit können Sie eine Funktion in einem separaten Thread ausführen. Grundverwendung:

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

Beispiele und API-Dokument: http://www.eslinstructor.net/ng-vkthread/demo/

- Vadim

vadimk
quelle
das ist fantastisch!!
MattE
@vadimk Ich bekomme diesen Fehler bei der Verwendung Ihres Plugins! vkThread ist keine Funktion
DevLoverUmar