Skip to content

Commit

Permalink
feat($resource): add support for cancelling requests
Browse files Browse the repository at this point in the history
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
  • Loading branch information
petebacondarwin committed Nov 23, 2015
1 parent 15de1d5 commit 7a480b3
Show file tree
Hide file tree
Showing 2 changed files with 1,119 additions and 1,159 deletions.
51 changes: 27 additions & 24 deletions src/ngResource/resource.js
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ angular.module('ngResource', ['ng']).
}
};

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

var noop = angular.noop,
forEach = angular.forEach,
Expand Down Expand Up @@ -493,6 +493,7 @@ angular.module('ngResource', ['ng']).

function resourceFactory(url, paramDefaults, actions, options) {
var route = new Route(url, options);
var qTimeouts = new HashMap();

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

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

Resource.cancelRequest = function(value) {
var qTimeout = qTimeouts.get(value);
if (qTimeout) {
qTimeout.resolve();
qTimeouts.remove(value);
}
};

Resource.prototype.toJSON = function() {
var data = extend({}, this);
delete data.$promise;
Expand All @@ -535,14 +544,9 @@ angular.module('ngResource', ['ng']).
delete action.timeout;
hasTimeout = false;
}
action.cancellable = hasTimeout ?
false : action.hasOwnProperty('cancellable') ?
action.cancellable : (options && options.hasOwnProperty('cancellable')) ?
options.cancellable :
provider.defaults.cancellable;

Resource[name] = function(a1, a2, a3, a4) {
var params = {}, data, success, error;
var params = {}, data, success, error, qTimeout;

/* jshint -W086 */ /* (purposefully fall through case statements) */
switch (arguments.length) {
Expand Down Expand Up @@ -597,35 +601,28 @@ angular.module('ngResource', ['ng']).
case 'params':
case 'isArray':
case 'interceptor':
case 'cancellable':
break;
case 'timeout':
httpConfig[key] = value;
break;
}
});

if (!isInstanceCall) {
if (!action.cancellable) {
value.$cancelRequest = angular.noop;
} else {
var deferred = $q.defer();
httpConfig.timeout = deferred.promise;
value.$cancelRequest = deferred.resolve.bind(deferred);
}
}

if (hasBody) httpConfig.data = data;
route.setUrlParams(httpConfig,
extend({}, extractParams(data, action.params || {}), params),
action.url);

var promise = $http(httpConfig).finally(function() {
if (value.$cancelRequest) value.$cancelRequest = angular.noop;
}).then(function(response) {
if (!isInstanceCall && !httpConfig.timeout) {
qTimeout = $q.defer();
httpConfig.timeout = qTimeout.promise;
qTimeouts.put(value, qTimeout);
}

var promise = $http(httpConfig).then(function(response) {

var data = response.data,
promise = value.$promise,
cancelRequest = value.$cancelRequest;
promise = value.$promise;

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

value.$cancelRequest = cancelRequest;
value.$resolved = true;

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

// The request has completed (either successfully or in error)
// Make sure that we cancel the timeout if there was one
promise.finally(function() {
if (qTimeout) qTimeout.reject();
qTimeouts.remove(value);
});

promise = promise.then(
function(response) {
var value = responseInterceptor(response);
Expand Down
Loading

0 comments on commit 7a480b3

Please sign in to comment.