Skip to content

feat(cancellation): Provide AbortSignal to queryFn - #2782

Merged
TkDodo merged 10 commits into
TanStack:masterfrom
jcready:abort-controller-cancellation
Oct 30, 2021
Merged

TkDodo merged 10 commits into
TanStack:masterfrom
jcready:abort-controller-cancellation

Conversation

@jcready

@jcready jcready commented Oct 10, 2021

Copy link
Copy Markdown
Contributor

All queryFn calls will now have an AbortSignal instance passed to them
as part of the QueryFunctionContext. In environments that support the
AbortController/AbortSignal the consumer may use this signal as a
way to get notified about the query being cancelled. Consumers no
longer need to add a .cancel() function to the result of queryFn()

const query = useQuery('todos', async ({ signal }) => {
  const res = await fetch('/todos', {
    method: 'get',
    // Pass the signal to your request
    signal,
  });

  const json = await res.json();
  let data = null;

  if (!signal.aborted) {
    // do expensive CPU transformation of data
    data = expensiveTransform(json);
  }

  return data;
});

The previous behavior for cancellation will continue to work. If a
.cancel() function is provided as the result of calling queryFn()
the AbortSignal will not be notified when the cancellation happens.
If the runtime environment does not support AbortController/AbortSignal
the old behavior will continue to work, but the signal provided to
the queryFn() will not be notified when the cancellation happens.

Ref #1671 (comment)
Ref #1671 (comment)

Fix #2775
Fix #1893
Fix #1265
Fix #886

All queryFn calls will now have an AbortSignal instance passed to them
as part of the QueryFunctionContext. In environments that support the
AbortController/AbortSignal the consumer may use this `signal` as a
way to get notified about the query being cancelled. Consumers no
longer need to add a `.cancel()` function to the result of `queryFn()`

```ts
const query = useQuery('todos', async ({ signal }) => {
  const res = await fetch('/todos', {
    method: 'get',
    // Pass the signal to your request
    signal,
  });

  const json = await res.json();
  let data = null;

  if (!signal.aborted) {
    // do expensive CPU transformation of data
    data = expensiveTransform(json);
  }

  return data;
});
```

The previous behavior for cancellation will continue to work. If a
`.cancel()` function is provided as the result of calling `queryFn()`
the AbortSignal will not be notified when the cancellation happens.
If the runtime environment does not support AbortController/AbortSignal
the old behavior will continue to work, but the `signal` provided to
the `queryFn()` will not be notified when the cancellation happens.

Ref TanStack#1671 (comment)
Ref TanStack#1671 (comment)

Fix TanStack#2775
Fix TanStack#1893
Fix TanStack#1265
Fix TanStack#886
@vercel

vercel Bot commented Oct 10, 2021 •

Copy link
Copy Markdown

This pull request is being automatically deployed with Vercel (learn more).
To see the status of your deployment, click below or on the icon next to each commit.

🔍 Inspect: https://vercel.com/tanstack/react-query/EP7LTfYpn1gxJSY8SYdAUyRN9RxD
✅ Preview: https://react-query-git-fork-jcready-abort-controller-c-59ac27-tanstack.vercel.app

@codesandbox-ci

codesandbox-ci Bot commented Oct 10, 2021 •

Copy link
Copy Markdown

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 16233b0:

Sandbox Source
tannerlinsley/react-query: basic-typescript Configuration
focused-fermat-p8lzj Issue #886

@jcready
jcready marked this pull request as ready for review October 14, 2021 15:22
Comment thread src/core/tests/query.test.tsx Outdated
@jcready

jcready commented Oct 15, 2021

Copy link
Copy Markdown
Contributor Author

Regarding the check failure below I thought the @babel/env preset would provide support for globalThis. Is that not the case? I can add the minimal code for a polyfill myself if my understand was incorrect.

@TkDodo

TkDodo commented Oct 22, 2021

Copy link
Copy Markdown
Collaborator

it looks like we only need globalThis to infer type definitions, so can't we just use window.AbortController ?

@jcready

jcready commented Oct 22, 2021

Copy link
Copy Markdown
Contributor Author

@TkDodo I already fixed the issue with globalThis

@TkDodo TkDodo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you 🙏. I'm still missing:

  • tests that actually use the provided signal, for both useQuery and useInfiniteQuery.
  • documentation of the feature, likely on this page: https://react-query.tanstack.com/guides/query-cancellation. we could separate it into the old approach and the new one, preferring the new one, and also stating why that is. You've already shown some usage examples in the discussion, which we could copy over I guess?

Comment thread src/core/utils.ts Outdated

@TkDodo TkDodo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is great work, thank you 🙏 . It will be exciting to ship this

Comment thread docs/src/pages/guides/query-cancellation.md
Comment thread docs/src/pages/guides/query-cancellation.md Outdated
Comment thread docs/src/pages/guides/query-cancellation.md Outdated
Comment thread docs/src/pages/guides/query-cancellation.md Outdated
Comment thread src/core/utils.ts Outdated
@jcready

jcready commented Oct 25, 2021

Copy link
Copy Markdown
Contributor Author

Despite the tests passing I'm fairly sure that this will fail in old browsers that since the MinimalAbortController implementation is using a WeakMap and it doesn't appear that the build system actually polyfills it.

@TkDodo

TkDodo commented Oct 25, 2021

Copy link
Copy Markdown
Collaborator

this will fail in old browsers

very old browsers :) WeakMap has pretty good compatibility: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap#browser_compatibility

Nevertheless, I now think not providing a signal at all is better as its less code.

@jcready

jcready commented Oct 26, 2021

Copy link
Copy Markdown
Contributor Author

Okay. Huge change. I've switched entirely away from trying to provide a minimal interface and just made the signal optional. If the runtime environment has an AbortController interface available we will always provide the AbortSignal. If the signal exists and is consumed/read from the query context provided to the query function then we consider the query "cancelable" and will attempt to revert the query state when all observers have been removed just like we used to do if the query function returned a cancel function.

@TkDodo TkDodo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wow, this looks really slick now 🙌

Comment thread src/core/query.ts
Comment thread src/core/query.ts Outdated
Comment thread docs/src/pages/guides/query-cancellation.md Outdated
Comment thread src/core/tests/query.test.tsx Outdated
@TkDodo

TkDodo commented Oct 26, 2021

Copy link
Copy Markdown
Collaborator

sorry for the conflicts - we had another PR that added something to the queryFunctionContext. I think it means you'll also need to adapt the test to account for that extra field

@jcready

jcready commented Oct 30, 2021

Copy link
Copy Markdown
Contributor Author

@TkDodo merged

@TkDodo
TkDodo merged commit e7a3207 into TanStack:master Oct 30, 2021
@tannerlinsley

Copy link
Copy Markdown
Member

🎉 This PR is included in version 3.30.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

@TkDodo

TkDodo commented Oct 30, 2021

Copy link
Copy Markdown
Collaborator

Thank you for this great contribution @jcready . I've announced it on twitter: https://twitter.com/TkDodo/status/1454521789938294787

please let me know if you have a twitter handle so I can add you to the shoutout: https://twitter.com/TkDodo/status/1454521795529355265

@jcready

jcready commented Oct 30, 2021

Copy link
Copy Markdown
Contributor Author

Thank you @TkDodo! It was a pleasure working with you on this. I don't have Twitter, but I appreciate the link to my GH.

@jcready
jcready deleted the abort-controller-cancellation branch October 30, 2021 20:02
@caub

caub commented Oct 31, 2021

Copy link
Copy Markdown

I don't really understand, the useQuery(key, cb) 's cb receive a signal, but how do you trigger cancel? is there a query.cancel() function added to the API?

@jcready

jcready commented Oct 31, 2021

Copy link
Copy Markdown
Contributor Author

@TkDodo

TkDodo commented Oct 31, 2021

Copy link
Copy Markdown
Collaborator

@caub queries will be cancelled automatically by RQ in some situations, like when the query goes to inactive state (=you’re not using it anymore) and there is a fetch pending. For manual cancellation, see the docs link above

@caub

caub commented Oct 31, 2021

Copy link
Copy Markdown

Ah ok thanks guys, so in all cases, whether we plan to cancel queries manually or not, it's better to pass down signal right? as react-query can make use of it as you just said (component unmounted, etc..)

@jcready

jcready commented Oct 31, 2021 •

Copy link
Copy Markdown
Contributor Author

@caub that is debatable :) though personally I will be using the signal in every query function.

One tradeoff is that for any query that is cancelled where either a .cancel() function is provided on the returned promise OR the signal from the query context is consumed:

@sjc5

sjc5 commented Nov 5, 2021

Copy link
Copy Markdown
Contributor

@TkDodo It might be helpful to mention the version number after which this feature is available in the docs. I was getting type errors with the destructured signal argument, and it took a bit of digging to realize this is a brand new feature (I was on 3.27.0). I would submit a PR with the update, but am not sure I have enough context to get the details right. Just a thought. Thanks for the great feature, @jcready -- very cool.

@TkDodo

TkDodo commented Nov 5, 2021

Copy link
Copy Markdown
Collaborator

@telekstr good idea. The version where we introduced it is 3.30.0: https://github.com/tannerlinsley/react-query/releases/tag/v3.30.0

Please contribute it to the Query cancellation docs page, thank you.

@sjc5

sjc5 commented Nov 5, 2021

Copy link
Copy Markdown
Contributor

@telekstr good idea. The version where we introduced it is 3.30.0: https://github.com/tannerlinsley/react-query/releases/tag/v3.30.0

Please contribute it to the Query cancellation docs page, thank you.

Sure thing, here you go: #2887

This branch was successfully deployed

1 active deployment
Preview — 16233b0e Deployed Oct 30, 2021 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

5 participants