Skip to content

Commit 7a480b3

Browse files
feat($resource): add support for cancelling requests
Introduced changes: - Deprecate passing a promise as `timeout` (for `$resource` actions). It never worked correctly anyway. Now a warning is logged (using `$log.debug()`) and the property is removed. - Provide a `cancelRequest` static method on the Resource that will abort the request (if it's not already completed or aborted). If there is a numeric `timeout` specified on the action's configuration, this method will have no effect. Example usage: ```js var Post = $resource('/posts/:id', {id: '@id'}, { get: { method: 'GET' } }); var currentPost = Post.get({id: 1}); ... // A moment later the user selects another post, so // we don't need the previous request any more Post.cancelRequest(currentPost); currentPost = Post.get({id: 2}); ... ``` BREAKING CHANGE: Using a promise as `timeout` is no longer supported and will log a warning. It never worked the way it was supposed to anyway. Before: ```js var deferred = $q.defer(); var User = $resource('/api/user/:id', {id: '@id'}, { get: {method: 'GET', timeout: deferred.promise} }); var user = User.get({id: 1}); // sends a request deferred.resolve(); // aborts the request // Now, we need to re-define `User` passing a new promise as `timeout` // or else all subsequent requests from `someAction` will be aborted User = $resource(...); user = User.get({id: 2}); ``` After: ```js var User = $resource('/api/user/:id', {id: '@id'}, { get: {method: 'GET'} }); var user = User.get({id: 1}); // sends a request User.cancelRequest(instance); // aborts the request user = User.get({id: 2}); ``` Fixes angular#9332 Closes angular#13050 Closes angular#13058
1 parent 15de1d5 commit 7a480b3

File tree

2 files changed

+1119
-1159
lines changed

2 files changed

+1119
-1159
lines changed

src/ngResource/resource.js

+27-24
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ angular.module('ngResource', ['ng']).
365365
}
366366
};
367367

368-
this.$get = ['$http', '$log', '$q', function($http, $log, $q) {
368+
this.$get = ['$http', '$log', '$q', '$$HashMap', function($http, $log, $q, HashMap) {
369369

370370
var noop = angular.noop,
371371
forEach = angular.forEach,
@@ -493,6 +493,7 @@ angular.module('ngResource', ['ng']).
493493

494494
function resourceFactory(url, paramDefaults, actions, options) {
495495
var route = new Route(url, options);
496+
var qTimeouts = new HashMap();
496497

497498
actions = extend({}, provider.defaults.actions, actions);
498499

@@ -515,6 +516,14 @@ angular.module('ngResource', ['ng']).
515516
shallowClearAndCopy(value || {}, this);
516517
}
517518

519+
Resource.cancelRequest = function(value) {
520+
var qTimeout = qTimeouts.get(value);
521+
if (qTimeout) {
522+
qTimeout.resolve();
523+
qTimeouts.remove(value);
524+
}
525+
};
526+
518527
Resource.prototype.toJSON = function() {
519528
var data = extend({}, this);
520529
delete data.$promise;
@@ -535,14 +544,9 @@ angular.module('ngResource', ['ng']).
535544
delete action.timeout;
536545
hasTimeout = false;
537546
}
538-
action.cancellable = hasTimeout ?
539-
false : action.hasOwnProperty('cancellable') ?
540-
action.cancellable : (options && options.hasOwnProperty('cancellable')) ?
541-
options.cancellable :
542-
provider.defaults.cancellable;
543547

544548
Resource[name] = function(a1, a2, a3, a4) {
545-
var params = {}, data, success, error;
549+
var params = {}, data, success, error, qTimeout;
546550

547551
/* jshint -W086 */ /* (purposefully fall through case statements) */
548552
switch (arguments.length) {
@@ -597,35 +601,28 @@ angular.module('ngResource', ['ng']).
597601
case 'params':
598602
case 'isArray':
599603
case 'interceptor':
600-
case 'cancellable':
601604
break;
602605
case 'timeout':
603606
httpConfig[key] = value;
604607
break;
605608
}
606609
});
607610

608-
if (!isInstanceCall) {
609-
if (!action.cancellable) {
610-
value.$cancelRequest = angular.noop;
611-
} else {
612-
var deferred = $q.defer();
613-
httpConfig.timeout = deferred.promise;
614-
value.$cancelRequest = deferred.resolve.bind(deferred);
615-
}
616-
}
617-
618611
if (hasBody) httpConfig.data = data;
619612
route.setUrlParams(httpConfig,
620613
extend({}, extractParams(data, action.params || {}), params),
621614
action.url);
622615

623-
var promise = $http(httpConfig).finally(function() {
624-
if (value.$cancelRequest) value.$cancelRequest = angular.noop;
625-
}).then(function(response) {
616+
if (!isInstanceCall && !httpConfig.timeout) {
617+
qTimeout = $q.defer();
618+
httpConfig.timeout = qTimeout.promise;
619+
qTimeouts.put(value, qTimeout);
620+
}
621+
622+
var promise = $http(httpConfig).then(function(response) {
623+
626624
var data = response.data,
627-
promise = value.$promise,
628-
cancelRequest = value.$cancelRequest;
625+
promise = value.$promise;
629626

630627
if (data) {
631628
// Need to convert action.isArray to boolean in case it is undefined
@@ -655,7 +652,6 @@ angular.module('ngResource', ['ng']).
655652
}
656653
}
657654

658-
value.$cancelRequest = cancelRequest;
659655
value.$resolved = true;
660656

661657
response.resource = value;
@@ -669,6 +665,13 @@ angular.module('ngResource', ['ng']).
669665
return $q.reject(response);
670666
});
671667

668+
// The request has completed (either successfully or in error)
669+
// Make sure that we cancel the timeout if there was one
670+
promise.finally(function() {
671+
if (qTimeout) qTimeout.reject();
672+
qTimeouts.remove(value);
673+
});
674+
672675
promise = promise.then(
673676
function(response) {
674677
var value = responseInterceptor(response);

0 commit comments

Comments
 (0)