Sondeo del servidor con AngularJS

86

Estoy tratando de aprender AngularJS. Mi primer intento de obtener nuevos datos cada segundo funcionó:

'use strict';

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

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

Cuando simulo un servidor lento durmiendo el hilo durante 5 segundos, espera la respuesta antes de actualizar la interfaz de usuario y establecer otro tiempo de espera. El problema es cuando reescribí lo anterior para usar módulos angulares y DI para la creación de módulos:

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

Esto solo funciona si la respuesta del servidor es rápida. Si hay algún retraso, envía una solicitud por segundo sin esperar una respuesta y parece borrar la interfaz de usuario. Creo que necesito usar una función de devolución de llamada. Lo intenté:

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

pero obtuve un error: "Error: destination.push no es una función" Esto se basó en los documentos para $ resource pero realmente no entendí los ejemplos allí.

¿Cómo hago que funcione el segundo enfoque?

Dave
fuente

Respuestas:

115

Debería llamar a la tickfunción en la devolución de llamada para query.

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

    (function tick() {
        $scope.data = Data.query(function(){
            $timeout(tick, 1000);
        });
    })();
};
abhaga
fuente
3
Excelente, gracias. No sabía que pudieras devolver la llamada allí. Eso resolvió el problema del spam. También moví la asignación de datos al interior de la devolución de llamada, lo que resolvió el problema de limpieza de la interfaz de usuario.
Dave
1
¡Me alegro de poder ayudar! Si esto solucionó el problema, puede aceptar esta respuesta para que otros más adelante también puedan beneficiarse de ella.
Abhaga
1
Suponiendo que el código anterior es para pageA y controllerA. ¿Cómo detengo este temporizador cuando navego a la página B y al controlador B?
Varun Verma
6
El proceso para detener un $ timeout se explica aquí docs.angularjs.org/api/ng.$timeout . Básicamente, la función $ timeout devuelve una promesa que debe asignar a una variable. Luego, escuche cuando ese controlador sea destruido: $ scope. $ On ('destroy', fn ()) ;. En la función de devolución de llamada, llame al método de cancelación de $ timeout y pase la promesa que guardó: $ timeout.cancel (timeoutVar); Los documentos de $ interval en realidad tienen un mejor ejemplo ( docs.angularjs.org/api/ng.$interval )
Justin Lucas
1
@JustinLucas, por si acaso debería ser $ scope. $ On ('$ destroy', fn ());
Tomate
33

Las versiones más recientes de angular han introducido $ interval, que funciona incluso mejor que $ timeout para el sondeo del servidor.

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;
    }
});
Beto
fuente
17
-1, no creo que $ interval sea adecuado, porque no puede esperar la respuesta del servidor antes de enviar la siguiente solicitud. Esto puede resultar en muchas solicitudes cuando el servidor tiene una latencia alta.
Treur
4
@Treur: Si bien eso parece ser una sabiduría convencional en estos días, no estoy seguro de estar de acuerdo. En la mayoría de los casos, prefiero tener una solución más resistente. Considere el caso en el que un usuario se desconecta temporalmente o el caso extremo de su caso en el que el servidor no responde a una sola solicitud. La interfaz de usuario dejará de actualizarse para los usuarios de $ timeout ya que no se establecerá un nuevo tiempo de espera. Para los usuarios de $ interval, la interfaz de usuario continuará donde se detuvo tan pronto como se restaure la conectividad. Obviamente, elegir retrasos sensatos también es importante.
Bob
2
Creo que es más conveniente, pero no resistente. (Un baño en mi habitación también es muy conveniente por la noche, pero eventualmente comenzará a oler mal;)) Cuando recuperas datos reales usando $ interval, ignoras el resultado del servidor. Este carece de un método para informar a tu usuario, facilitar la integridad de los datos o en definitiva: gestionar el estado de tu aplicación en general. Sin embargo, puede usar interceptores $ http comunes para esto y cancelar el intervalo $ cuando esto suceda.
Treur
2
Si usa $ q promesas, simplemente puede usar la devolución de llamada final para asegurarse de que el sondeo continúe si la solicitud falla o no.
Tyson Nero
8
Una mejor alternativa sería manejar no solo el evento de éxito, sino también el evento de error. De esta forma, puede volver a intentar la solicitud si falla. Incluso podría hacerlo en un intervalo diferente ...
Peanut
5

Aquí está mi versión con sondeo recursivo. Lo que significa que esperará la respuesta del servidor antes de iniciar el próximo tiempo de espera. Además, cuando ocurra un error, continuará la encuesta, pero de una manera más relajada y de acuerdo con la duración del error.

La demostración está aquí

Escrito más sobre esto aquí

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...';
   });
guya
fuente
0

Podemos hacerlo mediante el sondeo fácilmente usando el servicio $ interval. aquí hay un documento detallado sobre $ interval
https://docs.angularjs.org/api/ng/service/$interval El
problema al usar $ interval es que si está haciendo una llamada de servicio $ http o interacción con el servidor y si se demora más de $ intervalo de tiempo luego, antes de que se complete su solicitud, inicia otra solicitud.
Solución: 2. De alguna manera, todavía está sucediendo por cualquier motivo, debe marcar un indicador global de que la solicitud anterior terminó o no antes de enviar cualquier otra solicitud. Perderá ese intervalo de tiempo, pero no enviará la solicitud antes de tiempo. Además, si desea establecer un valor de umbral que después de algún valor de todos modos se debe establecer el sondeo, puede hacerlo de la siguiente manera. Aquí está un ejemplo de trabajo. explicado en detalle
1. El sondeo debe ser un estado simple que se obtiene del servidor como un solo bit o json liviano, por lo que no debe tomar más tiempo que el intervalo definido. También debe definir el tiempo del intervalo de manera adecuada para evitar este problema.


aquí

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;
    }
}]);
user1941574
fuente