Опрос сервера с помощью AngularJS

Я пытаюсь изучить AngularJS. Моя первая попытка получить новые данные каждую секунду:

'use strict';

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

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

Когда я имитирую медленный сервер, спящий поток в течение 5 секунд, он ждет ответа до обновления пользовательского интерфейса и установки другого тайм-аута. Проблема заключается в том, что я переписал выше, чтобы использовать Angular модули и DI для создания модуля:

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

Это работает только в том случае, если ответ сервера выполняется быстро. Если есть какая-то задержка, он рассылает 1 запрос за секунду, не дожидаясь ответа и, кажется, очищает пользовательский интерфейс. Я думаю, мне нужно использовать функцию обратного вызова. Я пробовал:

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

но получена ошибка: "Ошибка: destination.push не является функцией". Это было основано на документах для $resource, но я на самом деле не понимали примеры там.

Как заставить второй подход работать?

Ответ 1

Вы должны вызвать функцию tick в обратном вызове для query.

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

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

Ответ 2

В более поздних версиях angular введен $interval, который работает даже лучше, чем $timeout для серверный опрос.

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

Ответ 3

Вот моя версия, использующая рекурсивный опрос. Это означает, что он будет ждать ответа сервера перед началом следующего таймаута. Кроме того, при возникновении ошибки он будет продолжать опрос, но в более расслабленной усадьбе и в зависимости от продолжительности ошибки.

Демо здесь

Написано подробнее об этом здесь

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

Ответ 4

Мы можем легко сделать опрос, используя службу $interval. вот подробный документ о $interval
https://docs.angularjs.org/api/ng/service/$interval
Проблема с использованием $interval заключается в том, что если вы выполняете вызов службы HTTP или взаимодействие с сервером, и если задержка превышает интервал времени, то до того, как ваш один запрос завершен, он запускает другой запрос.
<Б > Решение:
1. Опрос должен быть простым получением статуса с сервера, как один бит или легкий json, поэтому не должно занимать больше времени, чем ваш определенный интервал времени. Вы также должны определить время интервала, чтобы избежать этой проблемы.
2. Как-то все это происходит по какой-либо причине, вы должны проверить глобальный флаг, что предыдущий запрос был завершен или нет, прежде чем отправлять какие-либо другие запросы. Он пропустит этот интервал времени, но он не отправит запрос преждевременно.
Также, если вы хотите установить пороговое значение, которое после некоторого значения должно быть установлено, то вы можете сделать это следующим образом.
Вот рабочий пример. подробно объяснил здесь

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