GithubHelp home page GithubHelp logo

Comments (17)

buhichan avatar buhichan commented on May 7, 2024 11

You are re-inventing rxjs's switchMap, why not just use rxjs and stop using this lib once and for all? And why this library not choose observable which can carry more information (like subscription) than Promise? There's no need to re-invent observable.

from recoil.

steve-taylor avatar steve-taylor commented on May 7, 2024 5

Search-as-you-type is an example of where requests should be cancelled, even after debouncing. And you’re unlikely to get a lot of cache hits.

from recoil.

drarmstr avatar drarmstr commented on May 7, 2024 4

Yes, you're right that cancelation could be implemented in the user code. Recoil tries to be minimal and not too opinionated on how the underlying queries are implemented, just working directly with simple Promises.

But, a potential concern with your particular example, though, is that it is depending on state outside of Recoil. The Recoil state is associated with the containing <RecoilRoot> for the particular React rendering tree that it's executing for. With React concurrent rendering there may be multiple trees executing that have different state values for the currentUserIDState atom.

Another problem is that the selector evaluation function may execute multiple times until it reaches completion. When it gets to a get that is still pending it may execute the beginning of the evaluation function again when it thinks the dependency may be available. In your case, if the current user ID state was itself asynchronous then the construction of the AbortController may happen multiple times. In this case it happens before the request, but if it happened after the request then re-execution would end up incorrectly canceling the first request.

So, yes, not being pure is breaking a rule. Though, we do make allowances for "caching". There are similar complications with trying to register cleanup handlers as part of the selector API. State consistency and, well, if the point is side-effects, then the selector isn't pure.

Query cancelation is likely an important use-case for many users, though, and deserves more thought on how to cleanly support it.

from recoil.

steve-taylor avatar steve-taylor commented on May 7, 2024 3

Query cancelation is likely an important use-case for many users, though, and deserves more thought on how to cleanly support it.

It certainly is! It would be a bit surprising if this became the recommended state management solution for React without fitting React like a glove, with respect to async cancellation. Obviously you’re not allowing async selectors to set state on unmounted components. Instead, pending Promises are being orphaned and swept under the rug, potentially leading to unnecessary resource exhaustion.

Overall, this is a great initiative. State management certainly needs a shake-up.

from recoil.

acutmore avatar acutmore commented on May 7, 2024 1

Hi @davidmccabe,

I suspect the long-term answer for this is to tie in with our caching strategy

This sounds very sensible. The requirements for the two are likely to be highly aligned.

The reason to cancel requests is because of resource constraints, right?

Yep, purely for resource constraints in mobile/embedded apps is what I was imagining.

It feels like Recoil's visibility of which selectors are currently in-use could be a good source of information to feed into request/cache garbage collection.

from recoil.

devarsh avatar devarsh commented on May 7, 2024 1

I also like how react-query allows us to pass a cancel fn on the promise object before returning the promise and when the result is stale it will call the cancel function on promise object.

Ref: https://react-query.tanstack.com/docs/guides/query-cancellation

[Edit]
I just realized this won't work when we call Promise.resolve(promiseWithCancelProperty)

from recoil.

acutmore avatar acutmore commented on May 7, 2024

Thank you for your detailed reply @drarmstr, very insightful.

If I am following correctly, the selector's get follows the same pattern seen in Suspense. Nested gets to other Selectors may throw if they are still pending just like useRecoilValue would. With this in mind I can see how user-implemented cancelation would not work. I'll edit my OP to make this clear to any future readers.

from recoil.

ammoradi avatar ammoradi commented on May 7, 2024

I think the cancelation of an asynchronous selector isn't Recoil's task.
because 1- it doesn't know what kind of operation is doing in its async selector (and shouldn't know).
and 2- a Recoil-Official-Async-Cancelation is not necessary when we can do this inside fetch/axion and so on.
Recoil maybe just do something stuff when it's resolved async data is on the air.

from recoil.

davidmccabe avatar davidmccabe commented on May 7, 2024

I suspect the long-term answer for this is to tie in with our caching strategy. Basically, you want to cancel a request when it's still ongoing and you would otherwise expunge it.

In our apps, we haven't wanted to cancel requests. This enables nice behaviors: For example, you can navigate to some state, which causes a request to begin, then while you're waiting you can go somewhere else in the app. When you return, your request will have finished and you'll be able to see the data immediately. Since Recoil was created for a desktop app that issued lots of slow queries, this was a good default.

The reason to cancel requests is because of resource constraints, right? Then you'll also want to be expunging unneeded data from cache. I know that in Relay, they expunge query results immediately when the QueryRenderer component unmounts.

This is when you'd want to cancel the request, right? When something is no longer needed, you expunge it from cache (if already finished) or cancel it (if ongoing).

from recoil.

steve-taylor avatar steve-taylor commented on May 7, 2024

1- it doesn't know what kind of operation is doing in its async selector (and shouldn't know).

There’s prior art in React: useEffect. The callback passed to useEffect can return an unsubscribe function, which can cancel pending requests and perform other resource cleanup. This standard is also used in Svelte and Bacon.js, for example. The difference is that the callback would need to take a sink function for providing the asynchronous result. It can be adapted to any async abstractions, such as promises, observables and callbacks.

from recoil.

lpolito avatar lpolito commented on May 7, 2024

So my current understanding is that there's no safe way to "clean up" asynchronous selectors? Or is there a valid work around until it's officially supported?

Namely I need to be able to abort fetches.

from recoil.

drarmstr avatar drarmstr commented on May 7, 2024

We have an idea for an API, but that is pending more work on memory management and cleanup.

from recoil.

xbreid avatar xbreid commented on May 7, 2024

So my current understanding is that there's no safe way to "clean up" asynchronous selectors? Or is there a valid work around until it's officially supported?

Namely I need to be able to abort fetches.

Is there any decent work around, or way to clean up async selectors then? and does anyone have an example of how they would do this? Is the best approach for now, to use useEffect on recoil state changes, and update state accordingly. This will kinda render selectors useless for me in some cases.

I need to be able to abort fetches as well.

from recoil.

csantos42 avatar csantos42 commented on May 7, 2024

@davidmccabe @drarmstr what if we expose the CANCELED (and maybe rename to something like CANCEL_SELECTOR) that is part of the selector core? This would provide a mechanism to bail out of adding results to the selector cache so that debouncing/retries/cancellations could potentially be implemented in user land. Note if we take that route we should modify getValFromRunningNewExecutionAndUpdatedDeps() to not add to cache when we get synchronous error equal to CANCEL_SELECTOR (which we might want to do regardless)

from recoil.

drarmstr avatar drarmstr commented on May 7, 2024

@csantos42 Hmm, yeah, that does sound like a potentially easy way to support this. I gather you intend that resolving an execution to Cancel would initiate a retry? Right now initializing an atom with a Promise in an atom effect can "abort" by resolving to DefaultValue. So, that might be a distinction. Also, with 0.6 now async selectors that resolve after a snapshot has been released will get "canceled" and the Canceled type is already exposed.

from recoil.

salvoravida avatar salvoravida commented on May 7, 2024

@drarmstr, any updates?

What about the first proposed solution?

const currentUserInfo = selector({
  key: 'CurrentUserInfo',
  get: async ({get, cleanup}) => {
    const controller = new AbortController();
    const signal = controller.signal;
    cleanup(() => controller.abort());

    const req = await fetch(`/user?id=${get(currentUserIDState)}`, {signal});
    return await req.json();
  },
});

it seems a clean api.

from recoil.

salvoravida avatar salvoravida commented on May 7, 2024

Currently, I'm doing this workaround:

let controller;
const currentUserInfo = selector({
  key: 'CurrentUserInfo',
  get: async ({get}) => {
    if (controller) controller.abort();
    controller = new AbortController();
    const signal = controller.signal;
    const req = await fetch(`/user?id=${get(currentUserIDState)}`, {signal});
    return await req.json();
  },
});

@drarmstr, any concerns?

from recoil.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.