Server-Polling mit AngularJS

Lesezeit: 7 Minuten

Benutzer-Avatar
David

Ich versuche, AngularJS zu lernen. Mein erster Versuch, jede Sekunde neue Daten zu bekommen, hat funktioniert:

'use strict';

function dataCtrl($scope, $http, $timeout) {
    $scope.data = [];

    (function tick() {
        $http.get('api/changingData').success(function (data) {
            $scope.data = data;
            $timeout(tick, 1000);
        });
    })();
};

Wenn ich einen langsamen Server simuliere, indem ich den Thread 5 Sekunden lang in den Ruhezustand versetze, wartet er auf die Antwort, bevor er die Benutzeroberfläche aktualisiert und ein weiteres Timeout festlegt. Das Problem ist, als ich das Obige umgeschrieben habe, um Angular-Module und DI für die Modulerstellung zu verwenden:

'use strict';

angular.module('datacat', ['dataServices']);

angular.module('dataServices', ['ngResource']).
    factory('Data', function ($resource) {
        return $resource('api/changingData', {}, {
            query: { method: 'GET', params: {}, isArray: true }
        });
    });

function dataCtrl($scope, $timeout, Data) {
    $scope.data = [];

    (function tick() {
        $scope.data = Data.query();
        $timeout(tick, 1000);
    })();
};

Dies funktioniert nur, wenn die Serverantwort schnell ist. Wenn es zu Verzögerungen kommt, spammt es 1 Anfrage pro Sekunde aus, ohne auf eine Antwort zu warten, und scheint die Benutzeroberfläche zu löschen. Ich denke, ich muss eine Callback-Funktion verwenden. Ich habe es versucht:

var x = Data.get({}, function () { });

bekam aber einen Fehler: “Error: destination.push is not a function” Dies basierte auf den Dokumenten für $ressource aber ich habe die Beispiele dort nicht wirklich verstanden.

Wie lasse ich den zweiten Ansatz funktionieren?

Sie sollten anrufen tick Funktion im Callback für query.

function dataCtrl($scope, $timeout, Data) {
    $scope.data = [];

    (function tick() {
        $scope.data = Data.query(function(){
            $timeout(tick, 1000);
        });
    })();
};

  • Großartig, danke. Ich wusste nicht, dass man den Rückruf dort platzieren kann. Damit war das Spam-Problem gelöst. Ich habe auch die Datenzuweisung in den Callback verschoben, wodurch das UI-Clearing-Problem gelöst wurde.

    – David

    2. Dezember 2012 um 17:25 Uhr


  • Angenommen, der obige Code gilt für pageA und controllerA. Wie stoppe ich diesen Timer, wenn ich zu pageB und controllerB navigiere?

    – Varun Verma

    21. November 2013 um 10:46 Uhr


  • Der Prozess zum Stoppen eines $timeouts wird hier erklärt docs.angularjs.org/api/ng.$timeout. Grundsätzlich gibt die $timeout-Funktion ein Versprechen zurück, das Sie einer Variablen zuweisen müssen. Hören Sie dann, wann dieser Controller zerstört wird: $scope.$on(‘destroy’, fn());. Rufen Sie in der Callback-Funktion die Cancel-Methode von $timeout auf und übergeben Sie das gespeicherte Promise: $timeout.cancel(timeoutVar); Die $interval-Dokumente haben tatsächlich ein besseres Beispiel (docs.angularjs.org/api/ng.$interval)

    – Justin Lukas

    15. Januar 2014 um 0:57 Uhr


  • @JustinLucas, nur für den Fall, dass es $scope.$on(‘$destroy’, fn()) sein sollte;

    – Tomate

    1. Juli 2014 um 2:18 Uhr

  • Was ist der beste Weg, um diesen Ansatz zu “stoppen”, sobald ein Ereignis auf meiner Seite auftritt. Ich brauche diesen Thread wirklich, wenn der Benutzer auf einer bestimmten Seite ist. Irgendeine Idee?

    – halbano

    23. Dezember 2014 um 20:26 Uhr

Benutzer-Avatar
Bob

Neuere Versionen von eckig wurden eingeführt $Intervall das funktioniert noch besser als $Zeitüberschreitung für die Serverabfrage.

var refreshData = function() {
    // Assign to scope within callback to avoid data flickering on screen
    Data.query({ someField: $scope.fieldValue }, function(dataElements){
        $scope.data = dataElements;
    });
};

var promise = $interval(refreshData, 1000);

// Cancel interval on page changes
$scope.$on('$destroy', function(){
    if (angular.isDefined(promise)) {
        $interval.cancel(promise);
        promise = undefined;
    }
});

  • -1, ich denke nicht, dass $interval geeignet ist, da Sie nicht auf die Serverantwort warten können, bevor Sie die nächste Anfrage senden. Dies kann zu zu vielen Anfragen führen, wenn der Server eine hohe Latenz hat.

    – Treu

    29. Januar 2014 um 16:17 Uhr


  • @Treur: Auch wenn das heutzutage gängige Weisheit zu sein scheint, bin ich mir nicht sicher, ob ich dem zustimme. In den meisten Fällen hätte ich lieber eine widerstandsfähigere Lösung. Betrachten Sie den Fall, in dem ein Benutzer vorübergehend offline geht, oder das Extrem Ihres Falls, in dem der Server nicht auf eine einzige Anfrage antwortet. Die Benutzeroberfläche wird für Benutzer von $timeout nicht mehr aktualisiert, da kein neues Timeout festgelegt wird. Für Benutzer von $interval fährt die Benutzeroberfläche dort fort, wo sie aufgehört hat, sobald die Verbindung wiederhergestellt ist. Natürlich ist es auch wichtig, vernünftige Verzögerungen auszuwählen.

    – Bob

    30. Januar 2014 um 2:58 Uhr

  • Ich denke, es ist bequemer, aber nicht belastbar. (Eine Toilette in meinem Schlafzimmer ist auch nachts sehr praktisch, aber irgendwann fängt sie an, schlecht zu riechen 😉 ) Wenn Sie aktuelle Daten mit $interval abrufen, ignorieren Sie das Ergebnis des Servers. Hier fehlt eine Methode, um Ihren Benutzer zu informieren, die Datenintegrität zu erleichtern oder kurz: Ihren Anwendungsstatus im Allgemeinen zu verwalten. Sie könnten jedoch gängige $http-Interceptoren dafür verwenden und das $interval abbrechen, wenn dies geschieht.

    – Treu

    30. Januar 2014 um 8:34 Uhr

  • Wenn Sie $q Promises verwenden, können Sie einfach den finally-Rückruf verwenden, um sicherzustellen, dass die Abfrage fortgesetzt wird, unabhängig davon, ob die Anfrage fehlschlägt oder nicht.

    – Tyson Nero

    25. April 2014 um 18:48 Uhr

  • Eine bessere Alternative wäre, nicht nur das Erfolgsereignis, sondern auch das Fehlerereignis zu behandeln. Auf diese Weise können Sie die Anfrage erneut versuchen, wenn sie fehlschlägt. Sie können es sogar in einem anderen Intervall tun …

    – Erdnuss

    16. Oktober 2014 um 14:41 Uhr

Hier ist meine Version mit rekursiver Abfrage. Das heißt, es wird auf die Serverantwort gewartet, bevor das nächste Timeout eingeleitet wird. Auch wenn ein Fehler auftritt, wird die Abfrage fortgesetzt, jedoch entspannter und entsprechend der Dauer des Fehlers.

Demo ist hier

Mehr dazu hier geschrieben

var app = angular.module('plunker', ['ngAnimate']);

app.controller('MainCtrl', function($scope, $http, $timeout) {

    var loadTime = 1000, //Load the data every second
        errorCount = 0, //Counter for the server errors
        loadPromise; //Pointer to the promise created by the Angular $timout service

    var getData = function() {
        $http.get('http://httpbin.org/delay/1?now=' + Date.now())

        .then(function(res) {
             $scope.data = res.data.args;

              errorCount = 0;
              nextLoad();
        })

        .catch(function(res) {
             $scope.data="Server error";
             nextLoad(++errorCount * 2 * loadTime);
        });
    };

     var cancelNextLoad = function() {
         $timeout.cancel(loadPromise);
     };

    var nextLoad = function(mill) {
        mill = mill || loadTime;

        //Always make sure the last timeout is cleared before starting a new one
        cancelNextLoad();
        $timeout(getData, mill);
    };


    //Start polling the data from the server
    getData();


        //Always clear the timeout when the view is destroyed, otherwise it will   keep polling
        $scope.$on('$destroy', function() {
            cancelNextLoad();
        });

        $scope.data="Loading...";
   });

Wir können es einfach mit dem Dienst $interval abfragen. Hier ist ein Detaildokument über $interval

https://docs.angularjs.org/api/ng/service/$interval

Problem Die Verwendung von $interval bedeutet, dass, wenn Sie einen $http-Dienst aufrufen oder eine Serverinteraktion ausführen und wenn die Verzögerung mehr als $interval beträgt, eine andere Anforderung gestartet wird, bevor Ihre eine Anforderung abgeschlossen ist.

Lösung:

1. Die Abfrage sollte ein einfacher Status sein, der vom Server abgerufen wird, z. B. ein einzelnes Bit oder ein leichtgewichtiger JSON, und sollte daher nicht länger als Ihre definierte Intervallzeit dauern. Sie sollten auch die Intervallzeit entsprechend definieren, um dieses Problem zu vermeiden.

2. Irgendwie passiert es aus irgendeinem Grund immer noch, Sie sollten ein globales Flag überprüfen, das die vorherige Anfrage abgeschlossen hat oder nicht, bevor Sie andere Anfragen senden. Es wird dieses Zeitintervall verpassen, aber es wird die Anfrage nicht vorzeitig senden.

Auch wenn Sie einen Schwellenwert festlegen möchten, nach dem nach einem bestimmten Wert sowieso die Abfrage festgelegt werden soll, können Sie dies auf folgende Weise tun.

Hier ist ein funktionierendes Beispiel. ausführlich erklärt hier

angular.module('myApp.view2', ['ngRoute'])
.controller('View2Ctrl', ['$scope', '$timeout', '$interval', '$http', function ($scope, $timeout, $interval, $http) {
    $scope.title = "Test Title";

    $scope.data = [];

    var hasvaluereturnd = true; // Flag to check 
    var thresholdvalue = 20; // interval threshold value

    function poll(interval, callback) {
        return $interval(function () {
            if (hasvaluereturnd) {  //check flag before start new call
                callback(hasvaluereturnd);
            }
            thresholdvalue = thresholdvalue - 1;  //Decrease threshold value 
            if (thresholdvalue == 0) {
                $scope.stopPoll(); // Stop $interval if it reaches to threshold
            }
        }, interval)
    }

    var pollpromise = poll(1000, function () {
        hasvaluereturnd = false;
        //$timeout(function () {  // You can test scenario where server takes more time then interval
        $http.get('http://httpbin.org/get?timeoutKey=timeoutValue').then(
            function (data) {
                hasvaluereturnd = true;  // set Flag to true to start new call
                $scope.data = data;

            },
            function (e) {
                hasvaluereturnd = true; // set Flag to true to start new call
                //You can set false also as per your requirement in case of error
            }
        );
        //}, 2000); 
    });

    // stop interval.
    $scope.stopPoll = function () {
        $interval.cancel(pollpromise);
        thresholdvalue = 0;     //reset all flags. 
        hasvaluereturnd = true;
    }
}]);

1153400cookie-checkServer-Polling mit AngularJS

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy