使用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秒钟来模拟慢速服务器时,它会等待响应,然后再更新UI和设置另一个超时。问题是当我重写以上内容以使用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个请求,并且似乎清除了UI。我想我需要使用回调函数。我试过了:

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

但是出现了一个错误:“错误:destination.push不是一个函数”这是基于$ resource的文档,但是我并不真正理解那里的示例。

如何使第二种方法起作用?

Answers:


115

您应该tick在的回调中调用该函数query

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

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

3
太好了,谢谢。我不知道您可以在其中放置回调。这解决了垃圾邮件问题。我还将数据分配移至回调内部,从而解决了UI清除问题。
戴夫

1
很高兴能够提供帮助!如果这解决了问题,则您可以接受此答案,以便其他人也可以从中受益。
abhaga

1
假设上面的代码是针对pageA和controllerA的。导航到pageB和controllerB时如何停止此计时器?
Varun Verma

6
docs.angularjs.org/api/ng.$timeout解释了停止$ timeout的过程。基本上,$ timeout函数返回一个Promise,您需要将其分配给变量。然后侦听该控制器何时销毁:$ scope。$ on('destroy',fn());。在回调函数中,调用$ timeout的cancel方法并传递您保存的承诺:$ timeout.cancel(timeoutVar); $ interval文档实际上有一个更好的示例(docs.angularjs.org/api/ng.$interval)–
贾斯汀·卢卡斯

1
@JustinLucas,以防万一应该是$ scope。$ on('$ destroy',fn());
番茄2014年

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 2014年

4
@Treur:虽然这些天这似乎是传统观念,但我不确定我是否同意。在大多数情况下,我宁愿有一个更具弹性的解决方案。考虑用户暂时离线的情况,或者服务器没有响应单个请求的极端情况。用户界面将停止为$ timeout用户更新,因为不会设置新的超时时间。对于$ interval的用户,恢复连接后,UI将从上次停止的地方开始提取。显然,选择合理的延迟也很重要。
鲍勃

2
我认为这更方便,但没有弹性。(我卧室的洗手间在晚上也很方便,但最终它会闻起来很臭;))当使用$ interval检索实际数据时,您将忽略服务器结果。缺少一种通知用户,促进数据完整性或简而言之的方法:通常管理应用程序状态。但是,您可以为此使用常见的$ http拦截器,并在发生这种情况时取消$ interval。
Treur 2014年

2
如果使用$ q promises,则可以简单地使用finally回调来确保无论请求是否失败,轮询都将继续。
泰森·尼罗

8
更好的选择是不仅处理成功事件,还处理错误事件。这样,如果请求失败,您可以再次尝试。您甚至可以以不同的间隔进行操作
花生2014年

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,然后在您的一个请求完成之前,它将启动另一个请求。
解决方案:
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;
    }
}]);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.