จะยกเลิกคำขอ $ http ใน AngularJS ได้อย่างไร


190

รับคำขอ Ajax ใน AngularJS

$http.get("/backend/").success(callback);

วิธีที่มีประสิทธิภาพมากที่สุดในการยกเลิกคำขอนั้นคือถ้ามีการเปิดตัวคำขออื่น (เช่นแบ็กเอนด์เดียวกันพารามิเตอร์ที่แตกต่างกัน)


8
ไม่มีคำตอบใดด้านล่างยกเลิกคำขอจริง ไม่มีวิธีที่จะยกเลิกการร้องขอ HTTP เมื่อออกจากเบราว์เซอร์ คำตอบด้านล่างทั้งหมดเพียงแค่ฟังผู้ฟังในลอนดอน คำขอ HTTP ยังคงส่งผลกระทบต่อเซิร์ฟเวอร์ยังคงดำเนินการอยู่และเซิร์ฟเวอร์จะยังคงส่งการตอบกลับเป็นเพียงกรณีที่ลูกค้ายังคงตอบรับการตอบกลับนั้นหรือไม่
เลียม

$ http ร้องขอยกเลิกการเปลี่ยนเส้นทางfreakyjolly.com/how-to-cancel-http-requests-in-angularjs-app
รหัส Spy

code สำหรับpromise.abort() stackoverflow.com/a/50415480/984780
Luis Perez

@ เลียมคำถามของฉันไม่ได้ยกเลิกบนเซิร์ฟเวอร์ นั่นจะเฉพาะเจาะจงมากกับเทคโนโลยีเซิร์ฟเวอร์ / การนำไปใช้งานของคุณ ฉันกังวลกับการละทิ้งการติดต่อกลับ
Sonic Soul

คำตอบ:


326

คุณลักษณะนี้ถูกเพิ่มไปยังรุ่น 1.1.5ผ่านพารามิเตอร์การหมดเวลา:

var canceler = $q.defer();
$http.get('/someUrl', {timeout: canceler.promise}).success(successCallback);
// later...
canceler.resolve();  // Aborts the $http request if it isn't finished.

14
ฉันควรทำอย่างไรในกรณีที่ฉันต้องการหมดเวลาและยกเลิกด้วยตนเองผ่านทางสัญญา
Raman Chodźka

15
@ RamanChodźkaคุณสามารถทำได้ทั้งสองอย่างโดยสัญญา คุณสามารถตั้งค่าการหมดเวลาเพื่อยกเลิกสัญญาหลังจากระยะเวลาหนึ่งไม่ว่าจะด้วยsetTimeoutฟังก์ชันดั้งเดิมของ JavaScript หรือ$timeoutบริการของ Angular
Quinn Strahl

9
canceler.resolve () จะยกเลิกการร้องขอในอนาคต นี่เป็นทางออกที่ดีกว่า: odetocode.com/blogs/scott/archive/2014/04/24/…
Toolkit

7
อีกตัวอย่างที่ดีของโซลูชันที่สมบูรณ์ยิ่งขึ้นจาก Ben Nadel: bennadel.com/blog/…
Pete

3
ใช้งานไม่ได้จริง ๆ คุณสามารถให้ตัวอย่างการทำงานได้หรือไม่?
Edward Olamisan

10

การยกเลิก Angular $ http Ajax ด้วยคุณสมบัติการหมดเวลาใช้งานไม่ได้ใน Angular 1.3.15 สำหรับผู้ที่ไม่สามารถรอให้สิ่งนี้ได้รับการแก้ไขฉันกำลังแบ่งปันโซลูชัน jQuery Ajax ที่ห่อด้วย Angular

การแก้ปัญหาเกี่ยวข้องกับสองบริการ:

  • HttpService (wrapper รอบฟังก์ชัน jQuery Ajax);
  • PendingRequestsService (ติดตามคำขอ Ajax ที่รอดำเนินการ / เปิด)

ต่อไปนี้คือบริการ PendingRequestsService

    (function (angular) {
    'use strict';
    var app = angular.module('app');
    app.service('PendingRequestsService', ["$log", function ($log) {            
        var $this = this;
        var pending = [];
        $this.add = function (request) {
            pending.push(request);
        };
        $this.remove = function (request) {
            pending = _.filter(pending, function (p) {
                return p.url !== request;
            });
        };
        $this.cancelAll = function () {
            angular.forEach(pending, function (p) {
                p.xhr.abort();
                p.deferred.reject();
            });
            pending.length = 0;
        };
    }]);})(window.angular);

บริการ HttpService:

     (function (angular) {
        'use strict';
        var app = angular.module('app');
        app.service('HttpService', ['$http', '$q', "$log", 'PendingRequestsService', function ($http, $q, $log, pendingRequests) {
            this.post = function (url, params) {
                var deferred = $q.defer();
                var xhr = $.ASI.callMethod({
                    url: url,
                    data: params,
                    error: function() {
                        $log.log("ajax error");
                    }
                });
                pendingRequests.add({
                    url: url,
                    xhr: xhr,
                    deferred: deferred
                });            
                xhr.done(function (data, textStatus, jqXhr) {                                    
                        deferred.resolve(data);
                    })
                    .fail(function (jqXhr, textStatus, errorThrown) {
                        deferred.reject(errorThrown);
                    }).always(function (dataOrjqXhr, textStatus, jqXhrErrorThrown) {
                        //Once a request has failed or succeeded, remove it from the pending list
                        pendingRequests.remove(url);
                    });
                return deferred.promise;
            }
        }]);
    })(window.angular);

ต่อมาในบริการของคุณเมื่อคุณกำลังโหลดข้อมูลคุณจะใช้ HttpService แทน $ http:

(function (angular) {

    angular.module('app').service('dataService', ["HttpService", function (httpService) {

        this.getResources = function (params) {

            return httpService.post('/serverMethod', { param: params });

        };
    }]);

})(window.angular);

ต่อมาในรหัสของคุณคุณต้องการโหลดข้อมูล:

(function (angular) {

var app = angular.module('app');

app.controller('YourController', ["DataService", "PendingRequestsService", function (httpService, pendingRequestsService) {

    dataService
    .getResources(params)
    .then(function (data) {    
    // do stuff    
    });    

    ...

    // later that day cancel requests    
    pendingRequestsService.cancelAll();
}]);

})(window.angular);

9

การยกเลิกคำขอที่ออกด้วย$httpไม่รองรับกับเวอร์ชันปัจจุบันของ AngularJS มีคำขอดึงที่เปิดเพื่อเพิ่มความสามารถนี้ แต่ PR นี้ยังไม่ได้รับการตรวจสอบดังนั้นจึงไม่ชัดเจนว่ามันจะทำให้มันเข้าไปในแกน AngularJS


PR ถูกปฏิเสธ OP ส่งอัปเดตที่นี่github.com/angular/angular.js/pull/1836
Mark Nadig

และนั่นก็ถูกปิดเช่นกัน
frapontillo

รุ่นของมันร่อนลงเช่นนี้ ยังคงพยายามหาไวยากรณ์เพื่อใช้เวอร์ชันสุดท้าย อยากให้ PRs มาพร้อมกับตัวอย่างการใช้งาน! :)
SimplGy

หน้าเอกสารเชิงมุมdocs.angularjs.org/api/ng/service/$httpใน 'การใช้งาน' อธิบายการตั้งค่าการหมดเวลาและยังกล่าวถึงสิ่งที่วัตถุ (สัญญา) ได้รับการยอมรับ
Igor Lino

6

หากคุณต้องการยกเลิกคำขอที่รอดำเนินการใน stateChangeStart ด้วย ui-router คุณสามารถใช้สิ่งนี้:

// อยู่ในการให้บริการ

                var deferred = $q.defer();
                var scope = this;
                $http.get(URL, {timeout : deferred.promise, cancel : deferred}).success(function(data){
                    //do something
                    deferred.resolve(dataUsage);
                }).error(function(){
                    deferred.reject();
                });
                return deferred.promise;

// ใน UIrouter config

$rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) {
    //To cancel pending request when change state
       angular.forEach($http.pendingRequests, function(request) {
          if (request.cancel && request.timeout) {
             request.cancel.resolve();
          }
       });
    });

สิ่งนี้ใช้ได้กับฉัน - ง่ายมากและฉันเพิ่มอีกสายหนึ่งเพื่อตั้งชื่อการโทรเพื่อให้ฉันสามารถเลือกการโทรและยกเลิกการรับสายบางอย่างเท่านั้น
Simon Dragsbæk

ทำไมการกำหนดค่า UI Router จำเป็นต้องทราบว่าrequest.timeoutมีอยู่จริง
trysis

6

ด้วยเหตุผลบางอย่าง config.timeout ใช้งานไม่ได้สำหรับฉัน ฉันใช้วิธีนี้:

let cancelRequest = $q.defer();
let cancelPromise = cancelRequest.promise;

let httpPromise = $http.get(...);

$q.race({ cancelPromise, httpPromise })
    .then(function (result) {
...
});

และ cancelRequest.resolve () เพื่อยกเลิก ที่จริงแล้วมันไม่ได้ยกเลิกการร้องขอ แต่คุณไม่ได้รับการตอบสนองที่ไม่จำเป็นอย่างน้อย

หวังว่านี่จะช่วยได้


คุณเห็น SyntaxError ของคุณ{ cancelPromise, httpPromise }หรือไม่
Mephiztopheles

นี่คือไวยากรณ์ของ ES6 คุณสามารถลอง {c: cancelPromise, h: httpPromise}
Aliaksandr Hmyrak

ฉันเห็นแล้วเครื่องมือ shortinitializer
Mephiztopheles

3

สิ่งนี้ช่วยเพิ่มคำตอบที่ได้รับการยอมรับโดยการตกแต่งบริการ $ http ด้วยวิธีการยกเลิกดังต่อไปนี้ ...

'use strict';
angular.module('admin')
  .config(["$provide", function ($provide) {

$provide.decorator('$http', ["$delegate", "$q", function ($delegate, $q) {
  var getFn = $delegate.get;
  var cancelerMap = {};

  function getCancelerKey(method, url) {
    var formattedMethod = method.toLowerCase();
    var formattedUrl = encodeURI(url).toLowerCase().split("?")[0];
    return formattedMethod + "~" + formattedUrl;
  }

  $delegate.get = function () {
    var cancelerKey, canceler, method;
    var args = [].slice.call(arguments);
    var url = args[0];
    var config = args[1] || {};
    if (config.timeout == null) {
      method = "GET";
      cancelerKey = getCancelerKey(method, url);
      canceler = $q.defer();
      cancelerMap[cancelerKey] = canceler;
      config.timeout = canceler.promise;
      args[1] = config;
    }
    return getFn.apply(null, args);
  };

  $delegate.abort = function (request) {
    console.log("aborting");
    var cancelerKey, canceler;
    cancelerKey = getCancelerKey(request.method, request.url);
    canceler = cancelerMap[cancelerKey];

    if (canceler != null) {
      console.log("aborting", cancelerKey);

      if (request.timeout != null && typeof request.timeout !== "number") {

        canceler.resolve();
        delete cancelerMap[cancelerKey];
      }
    }
  };

  return $delegate;
}]);
  }]);

รหัสนี้ทำอะไร?

หากต้องการยกเลิกคำขอต้องตั้งค่าการหมดเวลา "สัญญา" หากไม่มีการตั้งค่าการหมดเวลาในคำขอ HTTP รหัสจะเพิ่มการหมดเวลา "สัญญา" (หากตั้งค่าการหมดเวลาแล้วจะไม่มีการเปลี่ยนแปลงใด ๆ )

อย่างไรก็ตามเพื่อแก้ไขสัญญาที่เราต้องการจัดการกับ "รอการตัดบัญชี" เราจึงใช้แผนที่เพื่อให้เราสามารถเรียกข้อมูล "รอการตัดบัญชี" ได้ในภายหลัง เมื่อเราเรียกวิธีการยกเลิกจะมีการเรียก "รอการตัดบัญชี" จากแผนที่จากนั้นเราเรียกวิธีแก้ไขเพื่อยกเลิกการร้องขอ http

หวังว่านี่จะช่วยใครซักคน

ข้อ จำกัด

ปัจจุบันนี้ใช้งานได้กับ $ http.get เท่านั้น แต่คุณสามารถเพิ่มรหัสสำหรับ $ http.post และอื่น ๆ ได้

วิธีใช้ ...

จากนั้นคุณสามารถใช้มันตัวอย่างเช่นในการเปลี่ยนสถานะดังนี้ ...

rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
  angular.forEach($http.pendingRequests, function (request) {
        $http.abort(request);
    });
  });

ฉันกำลังสร้างแอพที่ยิงคำขอ http ในเวลาเดียวกันและฉันจำเป็นต้องยกเลิกด้วยตนเองทั้งหมด ฉันลองใช้รหัสของคุณแล้ว แต่จะยกเลิกการร้องขอล่าสุดเท่านั้น นั่นเคยเกิดขึ้นกับคุณมาก่อนหรือไม่? ความช่วยเหลือใด ๆ ที่จะได้รับการชื่นชม
Miguel Trabajo

1
รหัสที่นี่รักษาการค้นหาของการอ้างอิงไปยังวัตถุที่เลื่อนออกไปเพื่อให้พวกเขาสามารถเรียกคืนได้ในภายหลังเนื่องจากวัตถุที่เลื่อนออกไปจะต้องทำยกเลิก สิ่งสำคัญกับการค้นหาคือกุญแจ: คู่ของค่า ค่าเป็นวัตถุที่เลื่อนออกไป คีย์คือสตริงที่สร้างขึ้นตามวิธีการร้องขอ / url ฉันคาดเดาว่าคุณจะยกเลิกการร้องขอหลายครั้งด้วยวิธี / url เดียวกัน ด้วยเหตุนี้กุญแจทั้งหมดจึงเหมือนกันและพวกมันเขียนทับกันในแผนที่ คุณต้องปรับแต่งตรรกะการสร้างคีย์เพื่อให้มีการสร้างที่ไม่ซ้ำกันแม้ว่า url / method จะเหมือนกัน
danday74

1
ดำเนินการต่อจากด้านบน ... นี่ไม่ใช่ข้อผิดพลาดในรหัสรหัสจัดการการยกเลิกการร้องขอหลายครั้ง ... แต่รหัสก็ไม่เคยหมายถึงการจัดการกับการยกเลิกการร้องขอหลายรายการไปยัง URL เดียวกันโดยใช้วิธี http เดียวกัน ... แต่ หากคุณปรับเปลี่ยนตรรกะคุณควรจะสามารถทำให้มันทำงานได้อย่างง่ายดาย
danday74

1
ขอบคุณมาก! ฉันกำลังร้องขอหลายครั้งไปยัง URL เดียวกัน แต่ด้วยพารามิเตอร์ที่แตกต่างกันและหลังจากที่คุณพูดเกี่ยวกับว่าฉันเปลี่ยนบรรทัดนั้นและมันทำงานเหมือนมีเสน่ห์!
Miguel Trabajo

1

นี่คือรุ่นที่จัดการคำขอหลายรายการรวมทั้งตรวจสอบสถานะที่ถูกยกเลิกในการติดต่อกลับเพื่อระงับข้อผิดพลาดในบล็อกข้อผิดพลาด (ใน typescript)

ระดับตัวควบคุม:

    requests = new Map<string, ng.IDeferred<{}>>();

ใน http ของฉันได้รับ:

    getSomething(): void {
        let url = '/api/someaction';
        this.cancel(url); // cancel if this url is in progress

        var req = this.$q.defer();
        this.requests.set(url, req);
        let config: ng.IRequestShortcutConfig = {
            params: { id: someId}
            , timeout: req.promise   // <--- promise to trigger cancellation
        };

        this.$http.post(url, this.getPayload(), config).then(
            promiseValue => this.updateEditor(promiseValue.data as IEditor),
            reason => {
                // if legitimate exception, show error in UI
                if (!this.isCancelled(req)) {
                    this.showError(url, reason)
                }
            },
        ).finally(() => { });
    }

วิธีการช่วยเหลือ

    cancel(url: string) {
        this.requests.forEach((req,key) => {
            if (key == url)
                req.resolve('cancelled');
        });
        this.requests.delete(url);
    }

    isCancelled(req: ng.IDeferred<{}>) {
        var p = req.promise as any; // as any because typings are missing $$state
        return p.$$state && p.$$state.value == 'cancelled';
    }

ตอนนี้ดูที่แท็บเครือข่ายฉันเห็นว่ามันทำงานได้อย่างยอดเยี่ยม ฉันเรียกว่าวิธี 4 ครั้งและสุดท้ายเท่านั้นที่ผ่าน

ป้อนคำอธิบายรูปภาพที่นี่


req.resolve ( 'ยกเลิก'); ไม่ทำงานสำหรับฉันฉันใช้รุ่น 1.7.2 แม้ว่าฉันต้องการยกเลิกการโทรหากมีการโทรอีกครั้งและการโทรแรกยังอยู่ในสถานะรอดำเนินการ กรุณาช่วย. ฉันมักจะต้องการให้ข้อมูลการโทรที่เรียกใหม่โดยการยกเลิก URL เดียวกันของ api ที่ค้างอยู่ทั้งหมด
Sudarshan Kalebere

1

คุณสามารถเพิ่มฟังก์ชั่นที่กำหนดเองให้กับ$httpบริการโดยใช้ "มัณฑนากร" ที่จะเพิ่มabort()ฟังก์ชั่นให้กับสัญญาของคุณ

นี่คือรหัสการทำงานบางส่วน:

app.config(function($provide) {
    $provide.decorator('$http', function $logDecorator($delegate, $q) {
        $delegate.with_abort = function(options) {
            let abort_defer = $q.defer();
            let new_options = angular.copy(options);
            new_options.timeout = abort_defer.promise;
            let do_throw_error = false;

            let http_promise = $delegate(new_options).then(
                response => response, 
                error => {
                    if(do_throw_error) return $q.reject(error);
                    return $q(() => null); // prevent promise chain propagation
                });

            let real_then = http_promise.then;
            let then_function = function () { 
                return mod_promise(real_then.apply(this, arguments)); 
            };

            function mod_promise(promise) {
                promise.then = then_function;
                promise.abort = (do_throw_error_param = false) => {
                    do_throw_error = do_throw_error_param;
                    abort_defer.resolve();
                };
                return promise;
            }

            return mod_promise(http_promise);
        }

        return $delegate;
    });
});

รหัสนี้ใช้ฟังก์ชันการตกแต่งภายในของ angularjs เพื่อเพิ่มwith_abort()ฟังก์ชั่นการ$httpบริการ

with_abort()ใช้$httpตัวเลือกการหมดเวลาที่ช่วยให้คุณยกเลิกคำขอ http

สัญญาที่ส่งคืนถูกปรับเปลี่ยนเพื่อรวมabort()ฟังก์ชัน นอกจากนี้ยังมีรหัสเพื่อให้แน่ใจว่าการabort()ทำงานแม้ว่าคุณจะสัญญา

นี่คือตัวอย่างของวิธีที่คุณจะใช้:

// your original code
$http({ method: 'GET', url: '/names' }).then(names => {
    do_something(names));
});

// new code with ability to abort
var promise = $http.with_abort({ method: 'GET', url: '/names' }).then(
    function(names) {
        do_something(names));
    });

promise.abort(); // if you want to abort

โดยค่าเริ่มต้นเมื่อคุณโทรหาabort()คำขอจะถูกยกเลิกและไม่มีการเรียกใช้ตัวจัดการสัญญา

abort(true)หากคุณต้องการจัดการข้อผิดพลาดของคุณจะถูกเรียกว่าผ่านความจริงที่จะ

ในตัวจัดการข้อผิดพลาดของคุณคุณสามารถตรวจสอบว่า "ข้อผิดพลาด" เกิดจาก "ยกเลิก" โดยการตรวจสอบxhrStatusคุณสมบัติ นี่คือตัวอย่าง:

var promise = $http.with_abort({ method: 'GET', url: '/names' }).then(
    function(names) {
        do_something(names));
    }, 
    function(error) {
        if (er.xhrStatus === "abort") return;
    });
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.