if a Query is stopped using AbortController, it cannot be executed again.
See original GitHub issueIntended outcome:
i use react with apollo.
i want to cancel a running graphql query using the AbortController approach to stop the fetch request. later i want to run the query again.
Actual outcome:
i can cancel the query using an AbortController.but, if i execute the query again, no http-request is sent. maybe some cleanup did not happen and apollo still considers the query running? no idea. i can start other queries. so for example, if this graphql query has a parameter, i can start the query with param1, cancel it, then start it with param2 and it works. but if i start it again with param1, no http request is sent.
How to reproduce the issue:
- create a react-component
- wrap it in
withApollo - somewhere in the component do a handler to a button-click or something that you can do multiple times:
handleButtonClicked() {
const abortController = new AbortController();
const { signal } = abortController;
this.props.client
.query({
query: QUERY,
variables: {
.
.
.
},
context: {
fetchOptions: {
signal,
},
},
fetchPolicy: 'network-only',
})
.then(() => {console.log('done');});
setTimeout(() => {
abortController.abort();
}, 3000);
}
-
the
QUERYshould go to something slow (like, response in 20seconds) -
open the network-tab in the browser (i tried in google-chrome)
-
click the button, and verify that the query is first
pendingand then latercanceled -
click the button again, and there will be no new line in the network-tab
Versions
System:
OS: macOS 10.14.1
Binaries:
Node: 8.12.0 - /usr/local/opt/node@8/bin/node
Yarn: 1.12.1 - /usr/local/bin/yarn
npm: 6.4.1 - /usr/local/opt/node@8/bin/npm
Browsers:
Chrome: 70.0.3538.102
Firefox: 63.0.1
Safari: 12.0.1
Issue Analytics
- State:
- Created 5 years ago
- Reactions:26
- Comments:23 (5 by maintainers)
Top Related StackOverflow Question
I spent some time this weekend debugging this problem, and I found it was necessary to set both
queryDeduplication: falseon the ApolloClient and use.watchQueryinstead of.query+ an explicitAbortController.signal.I wound up with something like…
Creating the Apollo client:
Then to issue/cancel a query…
That should successfully cancel a query, and enable re-sending, e.g. in this case where I canceled a query twice, and let it succeed the third time:
@gabor where did you read about the AbortController approach? I think it’s the wrong way to do this, because reaching into the link/fetch and cancelling the request breaks all the abstractions that ApolloClient and apollo link set up for you. If you ask ApolloClient to run a query, then you should tell ApolloClient if you no longer wish to run that query, so the stack can be unwound in an orderly fashion. If you called
client.queryto run it, you should use that same interface to cancel the query. Unfortunately that’s not possible at the moment, but it should be possible by making the promise cancellable.Under the hood
client.querycallswatchQuery, which supports unsubscribing. If I’m not mistaken, unsubscribing from a watch query will propagate through the link stack and abort thefetchrequest in browsers that support it, so this is the proper way to go.If you want to make
querycancellable, you could consider making a PR to apollo client that adds acancelfunction to the promise returned.As a workaround for the time being, here are two other options:
watchQueryand unsubscribe from it when you want to cancel the request. You can turn an observable into a promise pretty easily. I’m not 100% sure if this will work, but it should work.client.linkdirectly to make the request by callingexecute(link, operation), as described here: https://www.apollographql.com/docs/link/#standalone If you unsubscribe from the observable there (by calling the function returned fromobservable.subscribe), the link stack should be properly unwound, and your fetch request will get cancelled if it’s still pending.PS: The reason the second request hangs if the first one was aborted is precisely because assumptions were violated by breaking the abstraction. As you can see here, the authors originally assumed that the only way a request would get aborted is via unsubscribing. If you’re aborting it directly, that assumption is obviously violated, and the error doesn’t propagate through the stack. This leaves other links and subscribers in the stack hanging, because they received neither an error, nor were they unsubscribed from. In your specific case, the dedup link is left hanging, so when a second identical request comes in, it ends up being deduplicated and your second requests waits for the first one to complete, not knowing that it was aborted. The simplest workaround in that case is to just passing
queryDeduplication: falsewhen you initially instantiate ApolloClient. Keep in mind however that this might now result in multiple identical queries being in flight at the same time if your client makes the same query more than once in a short interval.PPS: Looking at your code, it seems what you really want is just a query timeout. If so, then the right solution would be to use a timeout link (eg. apollo-link-timeout. Although that particular implementation issues and also breaks assumptions, it should fix your problem for now).