如何在 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) 的取消代码按钮