【问题标题】:How to cancel an $http request in AngularJS?如何在 AngularJS 中取消 $http 请求?
【发布时间】:2018-01-22 06:55:23
【问题描述】:

在 AngularJS 中给定一个 Ajax 请求

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

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

【问题讨论】:

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

标签: angularjs promise angular-promise angular-http cancellation


【解决方案1】:

这个特性是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/…
  • 真的没用。你能提供一个工作样本吗?
【解决方案2】:

使用 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);

【讨论】:

    【解决方案3】:

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

    【讨论】:

    【解决方案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】:

    由于某种原因 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 }
    • 我明白了,对象短初始化器
    【解决方案6】:

    这通过使用 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 方法...但是如果您调整逻辑,您应该能够相当轻松地使其工作。
    • 非常感谢!我向同一个网址发出了多个请求,但参数不同,在你说完之后我改变了那行,它就像一个魅力!
    【解决方案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() 时,请求会被取消,并且不会运行任何 Promise 处理程序。

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

    【讨论】:

      猜你喜欢
      • 2014-05-19
      • 1970-01-01
      • 2021-04-23
      • 2019-09-12
      • 2015-09-12
      相关资源
      最近更新 更多