apollo-feature-requests
apollo-feature-requests copied to clipboard
Implement timeout for Apollo Client
Currently there is no timeout mechanism in Apollo Client. The only way to achieve that is to create a custom link with the logic to timeout and cancel the inflight request.
Considering that Apollo uses the fetch interface, I feel this should be made available directly from the framework, and not rely on custom logic.
Implement this is straightforward as we can leverage the AbortSignal.timeout:
async function loadPosts() {
try {
const response = await fetch('/posts', {
signal: AbortSignal.timeout(5000)
});
const posts = await response.json();
return posts;
} catch (error) {
// Timeouts if the request takes longer than 5 seconds
console.log(error.name === 'AbortError');
// handle error
}
}
We could then be able to set a global timeout and override it on query basis passing a timeout option
Hey @lughino π
Thanks for the request! I'd like to poke at this a little bit to understand your thinking here.
HttpLink has the ability to set options on the fetch call via context.fetchOptions. With something like the setContext link, this can be used to set a timeout signal if not already provided in context as such:
setContext((_, previousContext) => ({
fetchOptions: {
signal: previousContext.fetchOptions?.signal || AbortSignal.timeout(5000)
}
})
You can override this per-query using context:
useQuery(query, {
context: {
fetchOptions: { signal: AbortSignal.timeout(10_000) }
}
});
HttpLink will use this signal if its provided.
Given the above, I'd like to understand how you envision something more integrated and what shortcomings there might be of the solution I posted above. I personally don't feel like this is a lot of code, and I'm not sure if something more integrated would be much more elegant. If we can avoid the bundle size in the library by using existing functionality, that would be ideal.
I may be shortsighted here, so please let me know what I'm missing! Thanks again for the request!
Hi @jerelmiller , Thanks for the quick reply!
Yes, it is possible to use the context in this way. I did not think of this solution, as my solution was to create a custom link with the timeout logic.
I would argue that the API exposed in this way is a bit obscure and not immediate. The link could handle this basic scenario directly, offering an explicit timeout option that can be looked from the interface of the hooks with an immediate understanding of the functionality.
I believe adding this would likely add no more than few bytes to the library bundle
I would argue that the API exposed in this way is a bit obscure and not immediate.
That's fair, but I'd consider solving this with documentation first π. If the above solution were a ton of code, I could definitely see a compelling reason to include it.
If you want to provide something more simple/out-of-the-box, would a third-party package be an option? If you're interested in publishing a package, we'd be happy to list it in our community links.
Something like
useQuery(query, {timeout: 10000})
is much more elegant than the proposed
setContext((_, previousContext) => ({
fetchOptions: {
signal: previousContext.fetchOptions?.signal || AbortSignal.timeout(5000)
}
})
plus
useQuery(query, {
context: {
fetchOptions: { signal: AbortSignal.timeout(10_000) }
}
});
That would imply that useQuery becomes http-aware, though, and right now all of these hooks and the Apollo Client core are transport protocol agnostic.
Hello! I have tried to use apollo-link-timeout with @defer and for a reason I can't explain it make the second payload received never processed. Commenting in the links chained the ApolloLinkTimeout instance maker @defer working like a charm.
I am curious if this feature request is something you planned to do
how does this work for apollo client 4
@rburgst no breaking changes, the examples from https://github.com/apollographql/apollo-feature-requests/issues/429#issuecomment-1989061258 above should work nicely for you.
If you go for the link approach, setContext is now deprecated but will still keep working. If you want to be future-proof, just change to the class notation:
const link = new SetContextLink((previousContext) => ({
fetchOptions: {
signal: previousContext.fetchOptions?.signal || AbortSignal.timeout(5000)
}
})
I did it easier with setting the fetchOptions on βnew HttpLinkβ
@rburgst I'm pretty sure that will timeout every request made in your application 5 (or what you configured) seconds after your app has started. You need a new AbortSignal every time, not reuse one
@phryneas you are right, thanks for the hint, it was too tempting (what tripped me up was the fact that fetchOptions is not a type in DefaultContext)
@rburgst are you on Apollo Client 4 by chance? If so, see our docs section on defining context types, specifically regarding built-in links. This should allow you to add those types to DefaultContext that can be used with all context options.
The example in the docs show fetchOptions in the context type π