Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

WIP - feat($resource): add support for cancelling requests using promises #13058

Conversation

gkalpak
Copy link
Member

@gkalpak gkalpak commented Oct 9, 2015

Previously, it was not possible to use a promise as value for the timeout property of an action's config, because that value would be copied before being passed to $http.

This commit introduces a special value for the timeout, namely 'promise'.
Setting an action's timeout configuration property to 'promise', will create a new promise and pass it to $http for each request. The associated deferred, is attached to the resource instance, under $timeoutDeferred.

Example usage:

var Post = $resource('/posts/:id', {id: '@id'}, {
  query: {
    method: 'GET',
    isArray: true,
    timeout: 'promise'
  }
});

var posts = Post.query();   // GET /posts
...
// Later we decide to cancel the request
// in order to make a new one
posts.$timeoutDeferred.resolve();
posts.query({author: 'me'});   // GET /posts?author=me
...

Fixes #9332
Closes #13050

Previously, it was not possible to use a promise as value for the
`timeout` property of an action's `config`, because that value would be
copied before being passed to `$http`.
This commit introduces a special value for the `timeout`, namely
`'promise'`. Setting an action's `timeout` configuration property to
`'promise'`, will create a new promise and pass it to `$http` for each
request. The associated deferred, is attached to the resource instance,
under `$timeoutDeferred`.

Example usage:

```js
var Post = $resource('/posts/:id', {id: '@id'}, {
  query: {
    method: 'GET',
    isArray: true,
    timeout: 'promise'
  }
});

var posts = Post.query();   // GET /posts
...
// Later we decide to cancel the request
// in order to make a new one
posts.$timeoutDeferred.resolve();
posts.query({author: 'me'});   // GET /posts?author=me
...
```

Fixes angular#9332
Closes angular#13050
@gkalpak
Copy link
Member Author

gkalpak commented Oct 9, 2015

This is a POC to get some feedback.

Still missing/pending (if we decide to move this forward):

  • Documentation
  • Decide whether to keep or remove the $timeoutDeferred property once receiving the response.
  • Decide how to handle the fact that $timeoutDeferred might not be present.
  • Decide if $timeoutDeferred is an appropriate name.

@darabos
Copy link

darabos commented Oct 9, 2015

I'm not qualified to review, but my opinion is that this is an excellent solution! Thanks a lot!

@petebacondarwin
Copy link
Contributor

@gkalpak I think that the fix in #12657 makes this unnecessary?

@gkalpak
Copy link
Member Author

gkalpak commented Oct 29, 2015

Not quite (e.g. see #12657 (comment) and next couple of comments).
With #12657 you need to recreate the resource "class" every time you cancel a request or it won't work correctly. It's not very practical imo.

This is adding better support for cancelling resource requests.

@petebacondarwin
Copy link
Contributor

I see! The feature is a good idea. I am not sure of the API, though.
What if we allowed people to pass in a promise to the instance methods?

@gkalpak
Copy link
Member Author

gkalpak commented Oct 29, 2015

I don't feel too strongly about the proposed API, but I would rather avoid the boiler plate of having to pass a promise every time (especially if we can easily avoid it :)).
Maybe we could just expose an extra method for cancelling the request and handle the promise creation/rejection internally (but then there's the overhead of creating a promise every time, just in case the user decides to use it).

I'm definitely open to better ideas though.

@petebacondarwin
Copy link
Contributor

Let's have a think about it.

@gkalpak
Copy link
Member Author

gkalpak commented Oct 29, 2015

Here's another idea (thinking loud):

  • Keep the current behaviour, when using timeout: <NUMBER> (in $resource).
  • Deprecate using timeout: <PROMISE> (in $resource), since we can't make it work correctly.
  • Introduce a new boolean property (in $resource's config - either per method or per class): cancellable. Iff this is true, we will add a new cancel()/abort() method on the returned instance/array of each method (and handle the promise creation and resolving internally).

(I wonder what other libs do. Have to take a look around.)

@gkalpak gkalpak closed this Oct 29, 2015
@gkalpak gkalpak reopened this Oct 29, 2015
@petebacondarwin
Copy link
Contributor

@gkalpak I like the sound of that better

@gkalpak
Copy link
Member Author

gkalpak commented Oct 30, 2015

From a quick look to other libs (Restangular, Angular Restmod, ModelCore, angular-restResource), they don't seem to have any special handling in place.
So, I'll go ahead and implement a quick POC as described in #13058 (comment).

@petebacondarwin
Copy link
Contributor

Good stuff @gkalpak

gkalpak added a commit to gkalpak/angular.js that referenced this pull request Oct 30, 2015
Introduced changes:

- Deprecate passing a promise as `timeout` (for `$resource` actions).
  It never worked correctly anyway.
  Now a warnign is logged (using `$log.debug()`) and the property is
  removed.
- Add support for a boolean `cancellable` property to actions'
  configuration, the `$resource` classes `options` of the
  `$resourceProvider`'s defaults.
  If true, the `$cancelRequest` method (added to all returned values for
  non-instance calls) will abort the request (if it's not already
  completed or aborted).
  If there is `timeout` specified on the action's configuration, the value
  of `cancellable` is ignored.

Example usage:

```js
var Post = $resource('/posts/:id', {id: '@id'}, {
  get: {
    method: 'GET',
    cancellable: true
  }
});

var currentPost = Post.get({id: 1});
...
// A moment later the user selects another post, so
// we don't need the previous request any more
currentPost.$cancelRequest();
currentPost = Post.get({id: 2});
...
```

Fixes angular#9332
Closes angular#13050
Closes angular#13058
@petebacondarwin petebacondarwin modified the milestones: 1.5.x - migration-facilitation, 1.6.x Nov 23, 2015
gkalpak added a commit to gkalpak/angular.js that referenced this pull request Nov 23, 2015
Introduced changes:

- Deprecate passing a promise as `timeout` (for `$resource` actions).
  It never worked correctly anyway.
  Now a warnign is logged (using `$log.debug()`) and the property is
  removed.
- Add support for a boolean `cancellable` property to actions'
  configuration, the `$resource` classes `options` of the
  `$resourceProvider`'s defaults.
  If true, the `$cancelRequest` method (added to all returned values for
  non-instance calls) will abort the request (if it's not already
  completed or aborted).
  If there is `timeout` specified on the action's configuration, the value
  of `cancellable` is ignored.

Example usage:

```js
var Post = $resource('/posts/:id', {id: '@id'}, {
  get: {
    method: 'GET',
    cancellable: true
  }
});

var currentPost = Post.get({id: 1});
...
// A moment later the user selects another post, so
// we don't need the previous request any more
currentPost.$cancelRequest();
currentPost = Post.get({id: 2});
...
```

Fixes angular#9332
Closes angular#13050
Closes angular#13058
gkalpak added a commit to gkalpak/angular.js that referenced this pull request Nov 23, 2015
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.
- Add support for a boolean `cancellable` property in actions'
  configuration, the `$resource` factory's `options` parameter and the
  `$resourceProvider`'s `defaults` property.
  If true, the `$cancelRequest` method (added to all returned values for
  non-instance calls) will abort the request (if it's not already
  completed or aborted).
  If there is a numeric `timeout` specified on the action's configuration,
  the value of `cancellable` will be ignored.

Example usage:

```js
var Post = $resource('/posts/:id', {id: '@id'}, {
  get: {
    method: 'GET',
    cancellable: true
  }
});

var currentPost = Post.get({id: 1});
...
// A moment later the user selects another post, so
// we don't need the previous request any more
currentPost.$cancelRequest();
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', cancellable: true}
});

var user = User.get({id: 1});   // sends a request
instance.$cancelRequest();      // aborts the request

user = User.get({id: 2});
```

Fixes angular#9332
Closes angular#13050
Closes angular#13058
petebacondarwin pushed a commit to petebacondarwin/angular.js that referenced this pull request Nov 23, 2015
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.
- Add support for a boolean `cancellable` property in actions'
  configuration, the `$resource` factory's `options` parameter and the
  `$resourceProvider`'s `defaults` property.
  If true, the `$cancelRequest` method (added to all returned values for
  non-instance calls) will abort the request (if it's not already
  completed or aborted).
  If there is a numeric `timeout` specified on the action's configuration,
  the value of `cancellable` will be ignored.

Example usage:

```js
var Post = $resource('/posts/:id', {id: '@id'}, {
  get: {
    method: 'GET',
    cancellable: true
  }
});

var currentPost = Post.get({id: 1});
...
// A moment later the user selects another post, so
// we don't need the previous request any more
currentPost.$cancelRequest();
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', cancellable: true}
});

var user = User.get({id: 1});   // sends a request
instance.$cancelRequest();      // aborts the request

user = User.get({id: 2});
```

Fixes angular#9332
Closes angular#13050
Closes angular#13058
petebacondarwin added a commit to petebacondarwin/angular.js that referenced this pull request Nov 23, 2015
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
petebacondarwin added a commit to petebacondarwin/angular.js that referenced this pull request Nov 23, 2015
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
@gkalpak gkalpak closed this in 98528be Nov 24, 2015
@gkalpak gkalpak deleted the feat-$resource-abort-request-using-promise branch November 26, 2015 08:27
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Can no longer cancel $resource request with a promise
4 participants