Polling server dengan AngularJS

86

Saya mencoba mempelajari AngularJS. Upaya pertama saya untuk mendapatkan data baru setiap detik berhasil:

'use strict';

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

    (function tick() {
        $http.get('api/changingData').success(function (data) {
            $scope.data = data;
            $timeout(tick, 1000);
        });
    })();
};

Ketika saya mensimulasikan server yang lambat dengan menidurkan utas selama 5 detik, ia menunggu respons sebelum memperbarui UI dan menyetel waktu tunggu lain. Masalahnya adalah ketika saya menulis ulang di atas untuk menggunakan modul Angular dan DI untuk pembuatan modul:

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

Ini hanya berfungsi jika respons server cepat. Jika ada penundaan, itu akan mengirimkan 1 permintaan per detik tanpa menunggu tanggapan dan tampaknya menghapus UI. Saya rasa saya perlu menggunakan fungsi panggilan balik. Saya mencoba:

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

tetapi mendapat kesalahan: "Error: destination.push is not a function" Ini didasarkan pada dokumen untuk $ resource tetapi saya tidak benar-benar memahami contoh di sana.

Bagaimana cara membuat pendekatan kedua berhasil?

Dave
sumber

Jawaban:

115

Anda harus memanggil tickfungsi dalam panggilan balik untuk query.

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

    (function tick() {
        $scope.data = Data.query(function(){
            $timeout(tick, 1000);
        });
    })();
};
abhaga
sumber
3
Luar biasa, terima kasih. Saya tidak tahu Anda bisa menelepon kembali di sana. Itu memecahkan masalah spamming. Saya juga memindahkan tugas data ke dalam callback yang memecahkan masalah pembersihan UI.
Dave
1
Senang bisa membantu! Jika ini menyelesaikan masalah, Anda dapat menerima jawaban ini sehingga nantinya orang lain juga bisa mendapatkan keuntungan darinya.
abhaga
1
Dengan asumsi kode di atas adalah untuk pageA dan controllerA. Bagaimana cara menghentikan timer ini saat saya menavigasi ke halamanB dan controllerB?
Varun Verma
6
Proses untuk menghentikan $ timeout dijelaskan di sini docs.angularjs.org/api/ng.$timeout . Pada dasarnya, fungsi $ timeout mengembalikan sebuah promise yang perlu Anda tetapkan ke variabel. Kemudian dengarkan saat pengontrol itu dihancurkan: $ scope. $ On ('destroy', fn ()) ;. Dalam fungsi panggilan balik, panggil metode pembatalan $ timeout dan berikan janji yang Anda simpan: $ timeout.cancel (timeoutVar); Dokumen $ interval sebenarnya memiliki contoh yang lebih baik ( docs.angularjs.org/api/ng.$interval )
Justin Lucas
1
@JustinLucas, untuk berjaga-jaga seandainya $ scope. $ On ('$ destroy', fn ());
Tomat
33

Versi angular yang lebih baru telah memperkenalkan $ interval yang bekerja lebih baik daripada $ timeout untuk polling server.

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;
    }
});
Bob
sumber
17
-1, menurut saya $ interval tidak cocok, karena Anda tidak bisa menunggu respons server sebelum mengirim permintaan berikutnya. Hal ini dapat mengakibatkan banyak permintaan saat server memiliki latensi tinggi.
Treur
4
@Treur: Meskipun itu tampaknya menjadi kebijaksanaan konvensional hari ini, saya tidak yakin saya setuju. Dalam kebanyakan kasus, saya lebih suka memiliki solusi yang lebih tangguh. Pertimbangkan kasus di mana pengguna offline sementara atau kasus ekstrem Anda di mana server tidak menanggapi satu permintaan. UI akan berhenti memperbarui untuk pengguna $ timeout karena waktu tunggu baru tidak akan disetel. Untuk pengguna $ interval, UI akan mengambil tempat yang ditinggalkannya segera setelah konektivitas pulih. Jelas memilih penundaan yang wajar juga penting.
Bob
2
Saya pikir ini lebih nyaman, tetapi tidak tangguh. (Toilet di kamar saya juga sangat nyaman di malam hari, tetapi pada akhirnya akan mulai berbau tidak enak;)) Saat mengambil data aktual menggunakan $ interval, Anda mengabaikan hasil server. Ini tidak memiliki metode untuk memberi tahu pengguna Anda, memfasilitasi integritas data, atau singkatnya: kelola status aplikasi Anda secara umum. Namun, Anda dapat menggunakan interseptor $ http umum untuk ini dan membatalkan $ interval saat ini terjadi.
Treur
2
Jika menggunakan $ q promise, Anda cukup menggunakan callback terakhir untuk memastikan polling berlanjut apakah permintaan gagal atau tidak.
Tyson Nero
8
Alternatif yang lebih baik adalah menangani tidak hanya peristiwa sukses, tetapi juga peristiwa kesalahan. Dengan cara ini Anda dapat mencoba permintaan lagi jika gagal. Anda bahkan mungkin melakukannya pada interval yang berbeda ...
Peanut
5

Ini versi saya menggunakan polling rekursif. Yang berarti itu akan menunggu respon server sebelum memulai waktu tunggu berikutnya. Selain itu, jika terjadi kesalahan, pemungutan suara akan dilanjutkan tetapi dengan cara yang lebih santai dan sesuai dengan durasi kesalahan.

Demo ada di sini

Lebih banyak ditulis di sini

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

Kita bisa melakukan polling dengan mudah menggunakan $ interval service. berikut adalah detail dokumen tentang $ interval
https://docs.angularjs.org/api/ng/service/$interval
Masalah menggunakan $ interval adalah jika Anda melakukan panggilan layanan $ http atau interaksi server dan jika tertunda lebih dari $ interval waktu lalu sebelum satu permintaan Anda selesai, itu memulai permintaan lain.
Solusi:
1. Polling harus berupa status sederhana yang didapat dari server seperti bit tunggal atau json ringan sehingga tidak memakan waktu lebih lama dari waktu interval yang Anda tentukan. Anda juga harus menentukan waktu interval dengan tepat untuk menghindari masalah ini.
2. Entah bagaimana itu masih terjadi karena alasan apapun, Anda harus memeriksa bendera global bahwa permintaan sebelumnya selesai atau tidak sebelum mengirim permintaan lain. Ini akan melewatkan interval waktu itu tetapi tidak akan mengirim permintaan sebelum waktunya.
Juga jika Anda ingin mengatur nilai ambang bahwa setelah beberapa nilai bagaimanapun polling harus diatur maka Anda dapat melakukannya dengan cara berikut.
Berikut adalah contoh kerja. dijelaskan secara rinci di sini

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