使用 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秒钟来模拟一个缓慢的服务器时,它会在更新 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);
})();
};

这只在服务器响应快的情况下有效。如果有任何延迟,它会在不等待响应的情况下每秒发出一个请求,并且似乎会清除 UI。我想我需要使用一个回调函数。我试过:

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

但是得到了一个错误: “ Error: destination.push is not a function”这是基于 $资源的文档,但是我并不真正理解那里的例子。

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

71244 次浏览

You should be calling the tick function in the callback for query.

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


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

More recent versions of angular have introduced $interval which works even better than $timeout for server polling.

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

Here is my version using recursive polling. Which means it'll wait for the server response before initiating the next timeout. Also, when an error occur it'll continue polling but in a more relaxed manor and according to the duration of the error.

Demo is here

Written more about it in here

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

We can do it polling easily using $interval service. here is detail document about $interval
https://docs.angularjs.org/api/ng/service/$interval
Problem using $interval is that if you are doing $http service calling or server interaction and if delayed more than $interval time then before your one request completes, it starts another request.
Solution:
1. Polling should be simple status getting from server like a single bit or lightweight json so should not take longer then your defined interval time. You should also define time of interval appropriately to avoid this issue.
2. Somehow it is still happening due any reason, you should check a global flag that previous request finished or not before sending any other requests. It will miss that time interval but it won't send request prematurely.
Also if you wanted to set threshold value that after some value anyhow polling should be set then you can do it following way.
Here is working example. explained in detail here

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