2012-12-02 75 views
83

我正在嘗試學習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秒鐘線程等待更新的用戶界面和設置其他超時之前的響應模擬一個緩慢的服務器。問題是,當我重寫使用角模塊和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,但我真的不知道那裏的例子。

如何使第二種方法有效?

回答

112

您應該在query的回調函數中調用tick函數。角

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

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

優秀的,謝謝。我不知道你可以把回調放在那裏。這解決了垃圾郵件問題。我還將數據分配移到了回調內部,從而解決了UI清除問題。 – David

+1

很高興能夠幫助!如果這解決了問題,您可以接受這個答案,以便其他人以後也可以從中受益。 – abhaga

+1

假設上述代碼適用於pageA和controllerA。當我導航到pageB和controllerB時,如何停止此計時器? –

32

較新版本的相繼推出$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; 
    } 
}); 
+13

-1,我不認爲$ interval是合適的,因爲在發送下一個請求之前你不能等待服務器響應。當服務器的延遲很高時,這可能會導致很多請求。 – Treur

+4

@Treur:儘管這似乎是傳統的智慧,這些天,我不知道我同意。在大多數情況下,我寧願有一個更有彈性的解決方案。考慮用戶暫時離線的情況,或者您的情況極端,即服務器不響應單個請求的情況。由於不會設置新的超時,用戶界面將停止更新$ timeout的用戶。對於$ interval的用戶,只要連接恢復,用戶界面就會自動停止。顯然選擇理智的延遲也很重要。 – Bob

+2

我認爲這樣更方便,但不夠靈活。 (我臥室裏的廁所在夜間也很方便,但最終會開始聞起來不好;))當使用$ interval檢索實際數據時,您會忽略服務器結果。這缺乏一種方法來通知您的用戶,方便數據完整性或簡而言之:通常管理您的應用程序狀態。 但是,您可以使用常見的$ http攔截器,並在發生這種情況時取消$ interval。 – Treur

1

這是我的版本使用遞歸輪詢。這意味着它會在啓動下一次超時之前等待服務器響應。 此外,當發生錯誤時,它會繼續輪詢,但在一個更寬鬆的莊園,並根據錯誤的持續時間。

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

我們可以做到這一點很容易地查詢使用$間隔服務。 這裏是詳細的文檔約$間隔
https://docs.angularjs.org/api/ng/service/$interval
問題使用$間隔,如果你正在做$ HTTP服務電話或服務器交互,如果延誤超過$間隔時間那麼你的一個請求完成後,前開始另一個請求。
解決方案:
1.查詢應該是簡單的狀態從服務器獲取像一個單個位或輕JSON所以不應該需要更長的時間,然後您定義的間隔時間。您還應該適當地定義時間間隔以避免此問題。
2.不知何故,它仍然因爲任何原因發生,您應該在發送任何其他請求之前檢查前一個請求是否完成的全局標誌。它將錯過該時間間隔,但不會過早發送請求。
此外,如果你想設置閾值,無論如何設置輪詢應該設置一些值,然後你可以按照下面的方式進行。
這裏是工作示例。在詳細解釋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; 
    } 
}]);