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


86

Я пытаюсь изучить 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, но я не совсем понял приведенные там примеры.

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

Ответы:


115

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

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

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

3
Отлично, спасибо. Я не знал, что вы можете добавить туда обратный звонок. Это решило проблему рассылки спама. Я также переместил назначение данных внутрь обратного вызова, который решил проблему очистки пользовательского интерфейса.
Дэйв

1
Рад возможности помочь! Если это решило проблему, вы можете принять этот ответ, чтобы другие позже также могли извлечь из него пользу.
abhaga 02

1
Предполагая, что приведенный выше код предназначен для pageA и controllerA. Как мне остановить этот таймер при переходе на страницу B и контроллер B?
Варун Верма

6
Процесс остановки тайм-аута $ объясняется здесь docs.angularjs.org/api/ng.$timeout . По сути, функция $ timeout возвращает обещание, которое необходимо присвоить переменной. Затем послушайте, когда этот контроллер будет уничтожен: $ scope. $ On ('destroy', fn ()) ;. В функции обратного вызова вызовите метод отмены $ timeout и передайте сохраненное обещание: $ timeout.cancel (timeoutVar); Документы $ interval на самом деле имеют лучший пример ( docs.angularjs.org/api/ng.$interval )
Джастин Лукас

1
@JustinLucas, на всякий случай это должна быть $ scope. $ On ('$ destroy', fn ());
Tomato

33

Более поздние версии 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;
    }
});

17
-1, я не думаю, что $ interval подходит, потому что вы не можете дождаться ответа сервера перед отправкой следующего запроса. Это может привести к множеству запросов, когда сервер имеет высокую задержку.
Treur

4
@Treur: Хотя в наши дни это кажется общепринятым, я не уверен, что согласен. В большинстве случаев я бы предпочел более гибкое решение. Рассмотрим случай, когда пользователь временно отключается от сети, или крайний случай, когда сервер не отвечает ни на один запрос. Пользовательский интерфейс перестанет обновляться для пользователей $ timeout, так как новый тайм-аут не будет установлен. Для пользователей $ interval пользовательский интерфейс возобновится с того места, где он остановился, как только соединение будет восстановлено. Очевидно, что выбор разумных задержек также важен.
Боб

2
Думаю, удобнее, но не надёжнее. (Туалет в моей спальне также очень удобен ночью, но со временем он начнет плохо пахнуть;)) При получении реальных данных с использованием $ interval вы игнорируете результат сервера. Здесь отсутствует метод информирования вашего пользователя, обеспечения целостности данных или, вкратце, управления состоянием вашего приложения в целом. Однако вы можете использовать для этого обычные перехватчики $ http и отменить интервал $, когда это произойдет.
Treur

2
Если вы используете обещания $ q, вы можете просто использовать обратный вызов finally, чтобы убедиться, что опрос продолжается независимо от того, завершился ли запрос или нет.
Тайсон Неро

8
Лучшей альтернативой было бы обрабатывать не только событие успеха, но и событие ошибки. Таким образом, вы можете повторить запрос, если он не удастся. Вы даже можете сделать это с другим интервалом ...
Peanut

5

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

Демо здесь

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

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

0

Мы можем легко выполнить опрос с помощью сервиса $ interval. вот подробный документ о $ interval
https://docs.angularjs.org/api/ng/service/$interval
Проблема с использованием $ interval заключается в том, что если вы выполняете вызов службы $ http или взаимодействие с сервером и если задержка превышает $ interval time затем, прежде чем ваш один запрос завершится, он запускает другой запрос.
Решение:
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;
    }
}]);
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.