Support asynchronous computed
What problem does this feature solve?
This would allow
- computation to rely on WebAPIs, for example IndexedDB
- implementation of event loop non-blocking computation
- implementation of debouncing / throttling or other custom use cases
What does the proposed API look like?
a = computed(async () => {
return await IndexedDB.get(id.value) + 5;
});
...
await a.value // get value
You could say this feature works out of the box, but unfortunately dependency on b is not tracked in this example
a = computed(async () => {
return await IndexedDB.get(id.value) + b.value;
});
A complete example would be helpful in understanding what your actual usecase is.
What's b
? Where would you await a computed? And are you aware that collection of reactive effects is inherently synchronous in all kinds of effects in Vue?
Have you tried https://vueuse.org/core/computedasync?
a = computed(async () => {
let b = b.value // read anything that need tracking before the await
return await IndexedDB.get(id.value) + b;
});
@sqal @lidlanca both of your proposed solutions have the same problem - I quote from https://vueuse.org/core/computedasync/
Dependencies that are accessed asynchronously will not trigger re-evaluation of the async computed value.
@lidlanca your proposed workaround does not cover some scenarios, for example
const getDependency = (key) => ...
const a = computed(async () => {
const c = await IndexedDB.get(id.value);
return getDependency(c).value;
});
@LinusBorg I try to keep examples laconic, so we do not dig into irrelevant details.
What's
b
?
Let's say b = ref(5)
. It could be any Vue computed / ref.
Where would you await a computed?
Anywhere where I want to use it, let's say before making request to some API.
And are you aware that collection of reactive effects is inherently synchronous in all kinds of effects in Vue?
Yes, I am aware. I don't see how it helps solving the problem.
irrelevant details.
I'm nto asking to get into irrelevant details, I'm asking to understand the use case and the kind of usage you have in mind.
for that, abstract one-line examples are not really useful.
Anywhere where I want to use it, let's say before making request to some API.
Can you write a short example how what would look like?
Yes, I am aware. I don't see how it helps solving the problem.
I wasn't saying it's solving the problem in any way. Quite to the contrary, I was pointing out that dependency collection in Vue is synchronous in general, not just computed, and as such is fundamentally unsuited, in its current form, for what I so far think you seem to want. This would be a pretty fundamental change.
@LinusBorg as I understand that's the point of having issues here in GitHub - discussing valuable changes (fundamental ones included). Please correct me if I'm wrong.
I provided few use cases in the very beginning:
- computation to rely on WebAPIs, for example IndexedDB
- implementation of event loop non-blocking computation
- implementation of debouncing / throttling or other custom use cases
In my opinion your questions / requests are irrelevant and exhausting, please don't be surprised if I stopped replying you.
I'm not sure how I gave the impression that I want to stop discussion. I just pointed out the scope. If I gave that impression, sorry for that.
Also feel free to stop replying. Just consider that we likely won't implement something that we don't see a need for ourselves "and* for which we don't have concrete use cases / expectations / scenarios. That's what I wanted to get more understanding on with my questions - out of curiosity. But you are free to not help me understand better.
Me failing to fully imagine the desired usage from your short descriptions of course doesn't mean that all of our team will have the same trouble that I have, so maybe someone will understand and implement it.
Good luck!
@LinusBorg with all due respect I guess that your teammates of higher seniority (for example @yyx990803) would understand the problem well. Vue is amazing, thank you for contributing to it.
const getDependency = (key) => ...
const a = computed(async () => { const c = await IndexedDB.get(id.value); return getDependency(c).value; });
this does not handle all possible use cases, but it address a way for the use case you presented.
const getDependency = (key) => ...
// using vueuse to simplify handling of a computed based on a promise.
const a = computedAsync(async () => {
return await IndexedDB.get(id.value);
});
const b = computed(()=>{
if(a.value){
return getDependency(a.value).value
}
})
@lidlanca I agree, this could be a workaround in many cases, thanks!
However an ideal solution in my opinion would be native support for async computed in @vue/reactivity. I have nice use cases for this library in Node.js environment where VueUse does not belong I assume.
Dependencies that are accessed asynchronously will not trigger re-evaluation of the async computed value.
This is a fundamental limitation of how Vue's reactivity system works, unless JavaScript provides a mechanism to capture function execution context across async ticks.
What you can do is to ensure the deps you are interested in are accessed synchronously before entering the first await
:
import { computedAsync } from '@vueuse/core'
a = computedAsync(async () => {
const idVal = id.value
const bVal = b.value
return await IndexedDB.get(idVal) + bVal;
});
A form of async computed that tracks deps across async ticks could potentially be done by using a generator function, but either way there is no plan to provide this feature in Vue core because:
- This is a niche case with a great amount of complexity.
- Vue provides enough primitives to do this in userland.
Side note: @donatas-luciunas your interaction with @LinusBorg has been patronizing and borderline rude. I'm sure you wouldn't be pleased if I imply that your request roots from a lack a good understanding of how Vue's reactivity tracking works. Please be respectful when interacting with maintainers who are trying to help you. If you continue with this attitude, don't be surprised for not getting any further replies from the team.
@yyx990803
This is a fundamental limitation of how Vue's reactivity system works, unless JavaScript provides a mechanism to capture function execution context across async ticks.
I agree that it is not possible with the API I described, but you could change the API
const getDependency = (key) => ...
const a = computed(async (value) => {
const c = await IndexedDB.get(value(id));
return value(getDependency(c));
});
where parameter value
is a unique function for every caller. Do you agree this could work?
What you can do is to ensure the deps you are interested in are accessed synchronously before entering the first await:
As I mentioned this workaround does not cover some scenarios, for example the one I provided above here.
This is a niche case with a great amount of complexity.
I do not agree on niche adjective. If you are interested let me share a bit of a vision. Let's assume @vue/reactivity
supports async computed.
For example we have 3 separate components that rely on the same peace of data that is retrieved with fetch
. We could use async computed for such scenario and minimal amount of fetch
invocations would be ensured (invoked with first call; cached until invalidated; recomputed if watched).
Imagine not one peace of data, but whole business model expressed as computed values some depending on others. You could use dependency injection container (could be inversify
for example) to avoid clutter. Again @vue/reactivity
would ensure minimal amount of fetch
invocations, which is more or less a target for every developer.
It seems that Vue tries to address this problem with async setup, but it has it's limitations. I wanted to offer this alternative approach, as it would ensure optimal runtime elegantly.
Is there a possibility you will reconsider supporting async computed?
I'm sure you wouldn't be pleased if I imply that your request roots from a lack a good understanding of how Vue's reactivity tracking works.
I believe we have a misunderstanding on what a "feature request" means. Do you mean that feature requests that are not easy to implement are not welcome?
If you continue with this attitude, don't be surprised for not getting any further replies from the team.
Why do you look at the speck of sawdust in your brother’s eye and pay no attention to the plank in your own eye? That's retorical.
...where parameter
value
is a unique function for every caller. Do you agree this could work?
No, because async dependency collection creates race conditions where each re-run of the async effect could produce different set of dependencies to be tracked. This is much more complicated than it may seem.
As I mentioned this workaround does not cover some scenarios, for example the one I provided above here.
You example is not technically valid, because the return value of IndexedDB.get
can never be a reactive dependency. Vue's reactivity system implies that reactive dependencies are created before the effects runs - they cannot be created on the fly during an effect (and then tracked by the effect itself).
Is there a possibility you will reconsider supporting async computed?
No. I still believe it's a niche case and most common scenarios can be solved by synchronously accessing necessary dependencies upfront before starting going into async land.
I believe we have a misunderstanding on what a "feature request" means. Do you mean that feature requests that are not easy to implement are not welcome?
As maintainers, we not only have to gauge the cost of implementing something, but also the cost of maintaining it forever afterwards. To ensure it is worth prioritizing over other things that we have to spend time on (which is limited), we also need to consider how many users would actually benefit from it. In this case, we have decided to not honor this request due to the reasons I have already outlined.
Coming back to your question: "feature request" means it is entirely up to the maintainer to decide whether to honor it or not. You can disagree with our reasoning behind the decision, but that doesn't make us in any way obliged to implement it, or even continue to engage in the conversation.
Why do you look at the speck of sawdust in your brother’s eye and pay no attention to the plank in your own eye? That's retorical.
I have looked at the comments from both sides and I do not see anything inappropriate from @LinusBorg, and certainly nothing that warrants the patronizing tone you have used against him.
Dear Evan,
Let's begin with the fact that this issue is not about me, it is about improvement idea.
The recurring problem I notice is that you and @LinusBorg put too little effort to grasp the idea (good indicators are your mistakes for example "return value of IndexedDB.get can never be a reactive dependency") and as a result you reject the idea without proper reason.
My role here is contributor / guide as I understand. I am willing to trade off sounding patronizing instead of killing the idea without proper reason. If you are looking for someone to blame, I'd argue that it is you and @LinusBorg for making me choose. A side action - you could think how to improve the way you guys work so great ideas are not rejected without proper reason. Of course you are not obligated to implement them, the correct way to handle is to keep issue hanging in my opinion.
Now let's discuss why am I convinced you do not have proper reason. Let's assume you should close the issue if value is too little and cost is too high.
This is much more complicated than it may seem.
This part is about cost.
I agree that it is not simple. If it was, I would have provided a pull request :)
I still believe it's a niche
This part is about value.
You compare your reactivity model to spreadsheet. Spreadsheet supports async.
Tracking only first call dependencies sounds like a bug. I'm not sure if niche is the right word here. You cannot prove it is not needed feature by the fact that people do not use this feature if the feature does not work.
Let's assume any data can be async data. Such assumption is not niche as that's the reason async / await were introduced.
Then encountering your dependencies tracking limitation is as niche as having if
in computed. In other words - not niche at all.
Do you agree? Or can you prove otherwise?
Please note maintainers have no obligation to spend more effort than they deem necessary. I have responded with as much patience as I can, explaining why we are not interested in implementing it. In short: not worth it in core, maybe doable in userland for those who really need it. Again, you can disagree with the reasoning, but you are in no position to demand anything. I have no interest in engaging further on this topic.