การสำรวจเซิร์ฟเวอร์ด้วย 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 และตั้งค่าการหมดเวลาอื่น ปัญหาคือเมื่อฉันเขียนด้านบนใหม่เพื่อใช้โมดูลเชิงมุมและ 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 ไม่ใช่ฟังก์ชัน" ซึ่งอ้างอิงจากเอกสารสำหรับทรัพยากร $แต่ฉันไม่เข้าใจตัวอย่างที่นั่นจริงๆ

ฉันจะทำให้แนวทางที่สองทำงานได้อย่างไร

คำตอบ:


115

คุณควรจะเรียกฟังก์ชั่นในการเรียกกลับสำหรับtickquery

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

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

3
ยอดเยี่ยมขอบคุณ ฉันไม่รู้ว่าคุณสามารถโทรกลับที่นั่นได้ ซึ่งช่วยแก้ปัญหาสแปมได้ ฉันยังย้ายการกำหนดข้อมูลไปยังภายในการโทรกลับซึ่งช่วยแก้ปัญหาการล้าง UI
Dave

1
ดีใจที่สามารถช่วยได้! หากวิธีนี้แก้ปัญหาได้คุณสามารถยอมรับคำตอบนี้เพื่อให้คนอื่นได้รับประโยชน์จากมัน
abhaga

1
สมมติว่าโค้ดด้านบนใช้สำหรับ pageA และ controllerA ฉันจะหยุดตัวจับเวลานี้ได้อย่างไรเมื่อไปที่ pageB และ controllerB
Varun Verma

6
กระบวนการในการหยุด $ หมดเวลาจะมีการอธิบายที่นี่docs.angularjs.org/api/ng.$timeout โดยทั่วไปฟังก์ชัน $ timeout จะส่งคืนสัญญาที่คุณต้องกำหนดให้กับตัวแปร จากนั้นฟังเมื่อคอนโทรลเลอร์นั้นถูกทำลาย: $ scope. $ on ('destroy', fn ()); ในฟังก์ชันการโทรกลับเรียกวิธีการยกเลิกของ $ timeout และส่งผ่านสัญญาที่คุณบันทึกไว้: $ timeout.cancel (timeoutVar); เอกสาร $ interval มีตัวอย่างที่ดีกว่าจริง ๆ ( docs.angularjs.org/api/ng.$interval )
Justin Lucas

1
@JustinLucas ในกรณีที่ควรเป็น $ scope $ on ('$ destroy', fn ());
มะเขือเทศ

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

4
@Treur: แม้ว่าวันนี้จะเป็นภูมิปัญญาดั้งเดิม แต่ฉันไม่แน่ใจว่าฉันเห็นด้วย ในกรณีส่วนใหญ่ฉันควรมีโซลูชันที่ยืดหยุ่นมากกว่านี้ พิจารณากรณีที่ผู้ใช้ออฟไลน์ชั่วคราวหรือกรณีของคุณขั้นรุนแรงที่เซิร์ฟเวอร์ไม่ตอบสนองต่อคำขอเดียว UI จะหยุดอัปเดตสำหรับผู้ใช้ $ timeout เนื่องจากจะไม่มีการตั้งค่าระยะหมดเวลาใหม่ สำหรับผู้ใช้ $ interval UI จะมารับจากจุดที่ค้างไว้ทันทีที่คืนค่าการเชื่อมต่อ เห็นได้ชัดว่าการเลือกความล่าช้าอย่างมีเหตุผลเป็นสิ่งสำคัญเช่นกัน
Bob

2
ฉันคิดว่ามันสะดวกกว่า แต่ไม่ยืดหยุ่น (ห้องน้ำในห้องนอนของฉันก็สะดวกมากในตอนกลางคืน แต่ในที่สุดมันก็จะเริ่มมีกลิ่นเหม็น;)) เมื่อดึงข้อมูลจริงโดยใช้ $ interval คุณจะไม่สนใจผลลัพธ์ของเซิร์ฟเวอร์ สิ่งนี้ไม่มีวิธีแจ้งผู้ใช้ของคุณอำนวยความสะดวกในความสมบูรณ์ของข้อมูลหรือเรียกสั้น ๆ ว่าจัดการสถานะแอปพลิเคชันของคุณโดยทั่วไป อย่างไรก็ตามคุณสามารถใช้ตัวดักจับ $ http ทั่วไปสำหรับสิ่งนี้และยกเลิก $ interval เมื่อสิ่งนี้เกิดขึ้น
Treur

2
หากใช้สัญญา $ q คุณสามารถใช้การโทรกลับในที่สุดเพื่อให้แน่ใจว่าการสำรวจดำเนินต่อไปไม่ว่าคำขอจะล้มเหลวหรือไม่
Tyson Nero

8
ทางเลือกที่ดีกว่าคือการจัดการไม่ใช่แค่เหตุการณ์ความสำเร็จเท่านั้น แต่ยังรวมถึงเหตุการณ์ข้อผิดพลาดด้วย ด้วยวิธีนี้คุณสามารถลองขออีกครั้งหากล้มเหลว คุณอาจจะทำในช่วงเวลาอื่นก็ได้ ...
ถั่วลิสง

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 time จากนั้นก่อนที่คำขอหนึ่งของคุณจะเสร็จสมบูรณ์คำขอจะเริ่มต้นอีกครั้ง
วิธีแก้ไข:
1. การโพลควรเป็นสถานะง่ายๆที่ได้รับจากเซิร์ฟเวอร์เช่น json บิตเดียวหรือเบาดังนั้นไม่ควรใช้เวลานานกว่านั้นตามช่วงเวลาที่คุณกำหนด คุณควรกำหนดช่วงเวลาให้เหมาะสมเพื่อหลีกเลี่ยงปัญหานี้
2. ยังคงเกิดขึ้นเนื่องจากเหตุผลใดก็ตามคุณควรตรวจสอบการตั้งค่าสถานะส่วนกลางว่าคำขอก่อนหน้านี้เสร็จสิ้นหรือไม่ก่อนที่จะส่งคำขออื่น ๆ จะพลาดช่วงเวลาดังกล่าว แต่จะไม่ส่งคำขอก่อนเวลาอันควร
นอกจากนี้หากคุณต้องการตั้งค่า threshold ซึ่งหลังจากค่าใดค่าหนึ่งแล้วควรตั้งค่าการสำรวจความคิดเห็นคุณสามารถทำได้ดังต่อไปนี้
นี่คือตัวอย่างการทำงาน อธิบายรายละเอียดที่นี่

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;
    }
}]);
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.