如何在 AngularJS 中取消 $http 请求?

Posted

技术标签:

【中文标题】如何在 AngularJS 中取消 $http 请求?【英文标题】:How to cancel an $http request in AngularJS? 【发布时间】:2012-12-05 08:51:39 【问题描述】:

在 AngularJS 中给定一个 Ajax 请求

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

如果启动另一个请求(例如相同的后端,不同的参数),取消该请求的最有效方法是什么。

【问题讨论】:

下面的答案都没有真正取消请求本身。一旦离开浏览器,就无法取消 HTTP 请求。以下所有答案都以某种方式简单地放弃了听众。 HTTP 请求仍然到达服务器,仍在处理,服务器仍然会发送响应,这只是客户端是否仍在侦听该响应的情况。 promise.abort()***.com/a/50415480/984780的代码 @Liam 我的问题没有在服务器上取消。这将非常具体到您的服务器技术/实现是什么。我担心放弃回调 【参考方案1】:

当前版本的 AngularJS 不支持取消使用 $http 发出的请求。有一个pull request opened 可以添加此功能,但此 PR 尚未经过审核,因此尚不清楚它是否会成为 AngularJS 核心。

【讨论】:

那个 PR 被拒绝了,OP 在这里提交了更新一个github.com/angular/angular.js/pull/1836 那也关门了。 它的一个版本landed as this。仍在尝试找出使用最终版本的语法。希望 PR 附带使用示例! :) 'Usage' 中的 Angular 文档页面 docs.angularjs.org/api/ng/service/$http 描述了超时设置,还提到了接受哪些对象(Promise)。【参考方案2】:

这个特性是added to the 1.1.5 release通过超时参数:

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

【讨论】:

如果我需要超时和通过 promise 手动取消,我该怎么办? @RamanChodźka 你可以通过承诺做到这两点;您可以设置超时以在一段时间后取消承诺,可以使用 javascript 的原生 setTimeout 函数或 Angular 的 $timeout 服务。 canceler.resolve() 将取消未来的请求。这是一个更好的解决方案:odetocode.com/blogs/scott/archive/2014/04/24/… 另一个来自 Ben Nadel 的更完整解决方案的好例子:bennadel.com/blog/… 真的没用。你能提供一个工作样本吗?【参考方案3】:

使用 timeout 属性取消 Angular $http Ajax 在 Angular 1.3.15 中不起作用。 对于那些迫不及待地想解决这个问题的人,我正在分享一个用 Angular 封装的 jQuery Ajax 解决方案。

解决方案涉及两个服务:

HttpService(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);

【讨论】:

【参考方案4】:

如果你想用 ui-router 取消 stateChangeStart 上的挂起请求,你可以使用这样的东西:

//服务中

                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 配置中

$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();
          
       );
    );

【讨论】:

这对我有用 - 非常简单,我添加了另一个来命名呼叫,这样我就可以选择呼叫并只取消一些呼叫 为什么 UI Router 配置需要知道request.timeout 是否存在?【参考方案5】:

这通过使用 abort 方法装饰 $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 请求上设置超时,那么代码会添加一个“承诺”超时。 (如果已经设置了超时,那么什么都不会改变)。

然而,为了解决承诺,我们需要处理“延迟”。 因此,我们使用映射,以便稍后检索“延迟”。 当我们调用abort方法时,从map中取出“deferred”,然后调用resolve方法取消http请求。

希望这对某人有所帮助。

限制

目前这仅适用于 $http.get 但您可以为 $http.post 等添加代码

如何使用...

然后您可以使用它,例如,在状态更改时,如下...

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

【讨论】:

我正在制作一个同时触发一些 http 请求的应用程序,我需要手动中止它们。我试过你的代码,但它只会中止最后一个请求。你以前有过这种情况吗?任何帮助将不胜感激。 这里的代码维护对延迟对象的引用的查找,以便以后可以检索它们,因为延迟对象需要执行中止。查找的重要部分是键:值对。该值是延迟对象。 key是根据请求方法/url生成的字符串。我猜您正在中止对同一方法/网址的多个请求。因此,所有键都是相同的,并且它们在地图中相互覆盖。您需要调整密钥生成逻辑,以便生成唯一的,即使 url / 方法相同。 从上面继续......这不是代码中的错误,代码处理中止多个请求......但代码根本不意味着处理使用中止对同一 url 的多个请求相同的 http 方法...但是如果您调整逻辑,您应该能够相当容易地使其工作。 非常感谢!我向同一个网址发出了多个请求,但参数不同,在你说完之后我改变了那行,它就像一个魅力!【参考方案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 了吗? 这是 ES6 语法,你可以试试 c: cancelPromise, h: httpPromise 我明白了,对象短初始化器【参考方案7】:

这是一个处理多个请求的版本,还检查回调中的取消状态以抑制错误块中的错误。 (在打字稿中)

控制器级别:

    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('cancelled');不适合我,我使用的是 1.7.2 版本。即使我想取消一个呼叫,如果再次呼叫并且第一个呼叫仍处于挂起状态。请帮忙。我总是想通过取消所有待处理的相同 url 的 api 来提供新调用的呼叫数据【参考方案8】:

您可以使用“装饰器”向$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() 时,请求会被取消,并且不会运行任何承诺处理程序。

如果您希望调用错误处理程序,请将 true 传递给 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;
    );

【讨论】:

以上是关于如何在 AngularJS 中取消 $http 请求?的主要内容,如果未能解决你的问题,请参考以下文章

我应该如何使用angularjs取消选择html页面中已经选择的复选框?

自定义 X-editable (AngularJS) 的取消代码按钮

在 routeChange 上取消 AngularJS $timeout

如何在AngularJS中丢弃预检响应

中止/取消/放弃angular js中的http请求并重试

调用后在angularjs中取消绑定$ watch