-
-
Save mzabriskie/ec3a25dd45dfa0de92c2 to your computer and use it in GitHub Desktop.
/** | |
* The problem with using Promises for a request API is that Promises | |
* make it difficult to abort the request. Typically when using XHR | |
* some factory will wrap the actual XHR, but ultimately will return | |
* the XHR object. | |
* | |
* ``` | |
* var request = performRequest('/user/12345'); | |
* request.abort(); | |
* ``` | |
* | |
* Using Promises, the Promise is what's returned. | |
* | |
* ``` | |
* var promise = performRequest('/user/12345'); | |
* promise.then((response) => { console.log(response); }); | |
* ``` | |
* | |
* How then can the request be aborted? | |
* | |
* Working on axios (https://github.com/mzabriskie/axios), | |
* I get a lot of requests (no pun intended) for how to | |
* handle aborting a request once it's been made. This is | |
* a potential workaround for aborting a request when using | |
* XHR with Promises. | |
*/ | |
// Setup request config and initiate the request | |
var config = { | |
method: 'get', | |
url: '/user/12345' | |
}; | |
axios(config); | |
// Internally axios will add a requestID property to the | |
// request config. This is an auto incrementing integer | |
// that is unique to the request. | |
console.log(config); // {method: 'get', url: '/user/12345', requestID: 0} | |
// Now the request can be aborted by passing the requestID | |
// to axios.abort. Behind the scenes axios will have a | |
// collection of requests indexed by requestID. The request | |
// can then be aborted if it hasn't already been fulfilled. | |
// This is very similar to how setTimeout/clearTimeout works. | |
axios.abort(config.requestID); |
Maybe instead of mutating the original config you could only allow aborting if requestID
is set beforehand. This way, a user could also specify custom IDs for different requests and most importantly would not have to keep the original config around between requests. As an added benefit one could cancel requests preemptively before starting new ones.
E.g.
axios({
requestId: 'user',
method: 'get',
url: '/user/12345'
});
// And then
axios.abort('user');
This could even be extended to support more useful cases: Subsequent requests with the same ID could also either auto-cancel a running previous request or return the same promise (so data doesn't get re-fetched).
axios({
requestId: 'user',
method: 'get',
url: '/user/888'
});
// This cancels the previous request
axios({
requestId: 'user',
cancelPreviousRequest: true,
method: 'get',
url: '/user/999'
});
// This returns the same promise like the previous request.
axios({
requestId: 'user',
method: 'get',
url: '/user/999'
});
👍 herrstucki's solution, that's looking nice.
I like the direction you've taken with this herrstucki. axios can then just track the requests that need to be tracked.
Any plans to include this solution in release?
bumping. Yes please include this for a future release if it hasn't already.
I like herrstucki idea. I would not use this often but when I would, I would like to have it clear and obvious what can and cannot be canceled
What about just adding a cancel method to the promise it returns? Seems a lot more straightforward than keeping track of request IDs. Cancellable promises aren't entirely unheard of.
The promise should also be rejected with an error clearly indicating that the request was cancelled to allow checking this during error handling and not triggering the usual self-healing code one would trigger if the request failed due to a real error.
I disagree with aborting triggering an error @pluma. I think that it should do nothing.
axios({
url: '/some/url',
requestId: 'whatever'
}).then(function (res) {
console.log('Request was successful');
}).catch(function (res) {
console.log('An error occurred');
});
axios.abort('whatever');
In this case, nothing should be logged. It's not an error, so it shouldn't hit the catch
. At the same time it's obviously not a successful request so it shouldn't enter then
either. It should simply abort the request, as if it was never made.
I agree with @pluma about adding the method to the returned promise. How about this API:
var request = axios({
url: '/some/url',
});
request.then(function (res) {
console.log('Request was successful');
}).catch(function (res) {
console.log('An error occurred');
});
request.abort();
So, the API is one thing, and error handling is another.
any news on this? I believe it should reject the promise with an Abort state. How would it work with es7 async await, e.g:
const { data } = await axios({
url: '/some/url',
});
In case the promise doesn't get rejected the code about would stuck forever.
Agreed with @esnunes. Aborting it is an error in the fulfillment of the promise's goal (which was to "return" the response data). Just because it's developer-initiated it doesn't mean it's not an error in the request flow.
Agreed with @herrstucki's comment. cancelPreviousRequest
would be pretty useful!
This doesn't seem to work on 0.15.3
. Is there a way to do it now? I see there are cancellation tokens but I want to be able to prevent duplicate requests to the same URL from executing if one is still pending.
@mzabriskie Any updates on this, interested to. abort()
isn't an option in the latest, cancelPreviousRequest
would be useful. I know the latest supports cancelTokens now but they don't work for me.
Yeah, mutating the config feels weird but I'd rather have that than nothing. It would satisfy my use case to to be able to set a callback in the config that gets passed the id/xhr object, but I'm not sure how much the synchronicity matters to others.