Skip to content

Commit 551a302

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 e15961e commit 551a302

File tree

2 files changed

+1119
-1159
lines changed

2 files changed

+1119
-1159
lines changed

src/ngResource/resource.js

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,7 @@ angular.module('ngResource', ['ng']).
417417
}
418418
};
419419

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

422422
var noop = angular.noop,
423423
forEach = angular.forEach,
@@ -545,6 +545,7 @@ angular.module('ngResource', ['ng']).
545545

546546
function resourceFactory(url, paramDefaults, actions, options) {
547547
var route = new Route(url, options);
548+
var qTimeouts = new HashMap();
548549

549550
actions = extend({}, provider.defaults.actions, actions);
550551

@@ -567,6 +568,14 @@ angular.module('ngResource', ['ng']).
567568
shallowClearAndCopy(value || {}, this);
568569
}
569570

571+
Resource.cancelRequest = function(value) {
572+
var qTimeout = qTimeouts.get(value);
573+
if (qTimeout) {
574+
qTimeout.resolve();
575+
qTimeouts.remove(value);
576+
}
577+
};
578+
570579
Resource.prototype.toJSON = function() {
571580
var data = extend({}, this);
572581
delete data.$promise;
@@ -587,14 +596,9 @@ angular.module('ngResource', ['ng']).
587596
delete action.timeout;
588597
hasTimeout = false;
589598
}
590-
action.cancellable = hasTimeout ?
591-
false : action.hasOwnProperty('cancellable') ?
592-
action.cancellable : (options && options.hasOwnProperty('cancellable')) ?
593-
options.cancellable :
594-
provider.defaults.cancellable;
595599

596600
Resource[name] = function(a1, a2, a3, a4) {
597-
var params = {}, data, success, error;
601+
var params = {}, data, success, error, qTimeout;
598602

599603
/* jshint -W086 */ /* (purposefully fall through case statements) */
600604
switch (arguments.length) {
@@ -649,35 +653,28 @@ angular.module('ngResource', ['ng']).
649653
case 'params':
650654
case 'isArray':
651655
case 'interceptor':
652-
case 'cancellable':
653656
break;
654657
case 'timeout':
655658
httpConfig[key] = value;
656659
break;
657660
}
658661
});
659662

660-
if (!isInstanceCall) {
661-
if (!action.cancellable) {
662-
value.$cancelRequest = angular.noop;
663-
} else {
664-
var deferred = $q.defer();
665-
httpConfig.timeout = deferred.promise;
666-
value.$cancelRequest = deferred.resolve;
667-
}
668-
}
669-
670663
if (hasBody) httpConfig.data = data;
671664
route.setUrlParams(httpConfig,
672665
extend({}, extractParams(data, action.params || {}), params),
673666
action.url);
674667

675-
var promise = $http(httpConfig).finally(function() {
676-
if (value.$cancelRequest) value.$cancelRequest = angular.noop;
677-
}).then(function(response) {
668+
if (!isInstanceCall && !httpConfig.timeout) {
669+
qTimeout = $q.defer();
670+
httpConfig.timeout = qTimeout.promise;
671+
qTimeouts.put(value, qTimeout);
672+
}
673+
674+
var promise = $http(httpConfig).then(function(response) {
675+
678676
var data = response.data,
679-
promise = value.$promise,
680-
cancelRequest = value.$cancelRequest;
677+
promise = value.$promise;
681678

682679
if (data) {
683680
// Need to convert action.isArray to boolean in case it is undefined
@@ -707,7 +704,6 @@ angular.module('ngResource', ['ng']).
707704
}
708705
}
709706

710-
value.$cancelRequest = cancelRequest;
711707
value.$resolved = true;
712708

713709
response.resource = value;
@@ -721,6 +717,13 @@ angular.module('ngResource', ['ng']).
721717
return $q.reject(response);
722718
});
723719

720+
// The request has completed (either successfully or in error)
721+
// Make sure that we cancel the timeout if there was one
722+
promise.finally(function() {
723+
if (qTimeout) qTimeout.reject();
724+
qTimeouts.remove(value);
725+
});
726+
724727
promise = promise.then(
725728
function(response) {
726729
var value = responseInterceptor(response);

0 commit comments

Comments
 (0)