使用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 () { });

但是出现了一个错误:"Error: destination.push is not a function",这是基于$resource文档的,但我并没有真正理解那里的示例。

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

4个回答

116
你应该在query的回调函数中调用tick函数。
function dataCtrl($scope, $timeout, Data) {
    $scope.data = [];

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

3
非常好,谢谢。我不知道你可以把回调函数放在那里。这解决了垃圾邮件问题。我还把数据分配移动到回调函数内部,解决了用户界面清除的问题。 - Dave
1
假设上述代码是针对页面A和控制器A的。当我导航到页面B和控制器B时,如何停止此计时器? - Varun Verma
6
停止 $timeout 的过程在这里解释:http://docs.angularjs.org/api/ng.$timeout。基本上,$timeout 函数返回一个 promise,你需要将其分配给一个变量。 然后监听该控制器何时被销毁:$scope.$on('destroy', fn());。在回调函数中调用 $timeout 的 cancel 方法并传入您保存的 promise:$timeout.cancel(timeoutVar)。实际上,$interval 文档有更好的示例(http://docs.angularjs.org/api/ng.$interval)。 - Justin Lucas
1
@JustinLucas,以防万一应该是$scope.$on('$destroy', fn()); - Tomato
当我的页面上发生某些事件时,最好的方法是“停止”这种方法。 当用户在特定页面上时,我确实需要这个线程运行。 有什么想法吗? - halbano
如何在Angular 2中实现相同的功能? - Vivek

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
我认为$interval不太合适,因为在发送下一个请求之前无法等待服务器的响应。当服务器延迟较高时,这可能会导致太多的请求。 - Treur
4
@Treur: 尽管这似乎是当今的常识,但我不确定是否同意。在大多数情况下,我更愿意有一个更具弹性的解决方案。考虑用户暂时离线的情况,或者极端情况下服务器未响应单个请求。对于 $timeout 的用户,由于不会设置新的超时,UI 将停止更新。对于 $interval 的用户,一旦恢复连接,UI 将从离开的地方继续更新。显然,选择合理的延迟时间也很重要。 - Bob
2
我认为这更方便,但不够弹性。(卧室里的厕所晚上也很方便,但最终会开始散发恶臭;)当使用$interval检索实际数据时,您会忽略服务器的结果。这缺乏一种方法来通知用户、促进数据完整性或简单地说:总体管理您的应用程序状态。 然而,您可以使用常见的$http拦截器来取消$interval。 - Treur
2
如果使用$q promises,您可以简单地使用finally回调来确保轮询继续进行,无论请求是否失败。 - Tyson Nero
8
更好的选择是不仅处理成功事件,还要处理错误事件。这样,如果请求失败,您就可以尝试重新发送请求。甚至可以在不同的时间间隔内进行尝试。 - Peanut

6
这是我的版本,使用递归轮询。这意味着它会在启动下一个超时之前等待服务器响应。 此外,当出现错误时,它会以更轻松的方式继续轮询,并根据错误的持续时间进行操作。

这里是演示

这里有更多关于它的内容

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
问题 是,如果您正在进行 $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;
    }
}]);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接