-
-
Notifications
You must be signed in to change notification settings - Fork 98
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Computed signal with async callback #284
Comments
In the meantime I have played with an additional method that looks like this: computed.await = function(v, cb = arguments.length === 1 ? v : cb) {
const s = signal(arguments.length === 2 ? v : undefined);
effect(() => cb().then((v) => s.value = v));
return computed(() => s.value);
}; where This extension can eg. be used for loading a page using a promise in a oneliner like below: const page = computed.await('Loading...', async () => (await fetch('app.html')).text());
render(HTML`<div>${page}</div>`); In something like that possible with Signals at the current moment or maybe worth being added? In case an addition is not aimed for, is there any feedback if the method above is save to use or is it messing to much with some internals that I am not aware of? Thanks for your feedback. |
This comment was marked as spam.
This comment was marked as spam.
I have an RFC open #291 and a POC implementation main...async-resource 😅 |
@JoviDeCroock do you know if any further work has been done on this? It looks like you closed your RFC? |
Some kind of standard solution here would be awesome. 😉 |
Folks testing out the branch I created could go a pretty long way in getting this out there 😅 I don't personally have good testing grounds at the current time |
I've implemented a resource and tested it: https://www.npmjs.com/package/@preact-signals/utils |
The problem with any sort of async stuff with Signals right now is that when you do The problem is that as soon as you do I don't know how to address this problem, even with significant changes to how signal tracking is done. Users can deal with this by making sure to reference any signals they care about before the first You can see the problem illustrated here: import { computed, signal } from '@preact/signals-core'
import * as process from 'node:process'
async function sleep(milliseconds: number) { await new Promise(resolve => setTimeout(resolve, milliseconds)) }
async function main() {
const apple = signal(3n)
const banana = signal(5n)
// create a computed signal that is derived from the contents of both of the above signals
const cherry = computed(async () => {
// apple is "read" before the await
apple.value
// sleep for a negligible amount of time to ensure the rest of this function runs in a continuation
await sleep(1)
// banana is "read" after the await
banana.value
// final computed value
return apple.value + banana.value
})
console.log(`Apple: ${apple.value}; Banana: ${banana.value}; Cherry: ${await cherry.value}`)
// Apple: 3; Banana: 5; Cherry: 8
// change the value of apple and notice that Cherry correctly tracks that change
apple.value = 4n
console.log(`Apple: ${apple.value}; Banana: ${banana.value}; Cherry: ${await cherry.value}`)
// Apple: 4; Banana: 5; Cherry: 9
// change the value of banana, and notice that Cherry is **not** recomputed (should be 10)
banana.value = 6n
console.log(`Apple: ${apple.value}; Banana: ${banana.value}; Cherry: ${await cherry.value}`)
// Apple: 4; Banana: 6; Cherry: 9
// change the value of apple again, and notice that Cherry is correctly recomputed using the value of banana set above
apple.value = 11n
console.log(`Apple: ${apple.value}; Banana: ${banana.value}; Cherry: ${await cherry.value}`)
// Apple: 11; Banana: 6; Cherry: 17
}
main().then(() => process.exit(0)).catch(error => { console.error(error); process.exit(1) }) |
@MicahZoltu There is no reactivity system that handles this case, because of design of js. Main solution is to use I've implemented analog for Another solution is to not use async functions but generator functions: import { Effect } from "effect"
const increment = (x: number) => x + 1
const divide = (a: number, b: number): Effect.Effect<never, Error, number> =>
b === 0
? Effect.fail(new Error("Cannot divide by zero"))
: Effect.succeed(a / b)
// $ExpectType Effect<never, never, number>
const task1 = Effect.promise(() => Promise.resolve(10))
// $ExpectType Effect<never, never, number>
const task2 = Effect.promise(() => Promise.resolve(2))
// $ExpectType Effect<never, Error, string>
export const program = Effect.gen(function* (_) {
const a = yield* _(task1)
const b = yield* _(task2)
const n1 = yield* _(divide(a, b))
const n2 = increment(n1)
return `Result is: ${n2}`
})
Effect.runPromise(program).then(console.log) // Output: "Result is: 6" https://www.effect.website/docs/essentials/using-generators#understanding-effectgen |
The pattern followed by I don't see how generators would help solve the problem where we want to re-run some async work when a signal that is read after the async work is touched. const mySignal = signal(5)
useComputed(async () => {
// mySignal.value
const result = await fetch(...)
const fetchedValue = getValueFromResult(result)
return fetchedValue + mySignal.value
} In this situation, we want to re-fetch the value when |
You need to write your own wrapper around generators which will start tracking after some promise resolves |
Write it with resource in this way const [resource, { refetch }] = createResource({
source: () => ({ mySignal: mySignal.value }),
fetcher: async ({ mySignal }) => {
const result = await fetch(...)
const fetchedValue = getValueFromResult(result)
return fetchedValue + mySignal
},
}); |
This doesn't seem much better than just doing this (which works): const mySignal = signal(5)
useComputed(async () => {
mySignal.value
const result = await fetch(...)
const fetchedValue = getValueFromResult(result)
return fetchedValue + mySignal.value
} Having the |
I don't think this case should be handled. You have primitives for this case, if you want - you can write your own wrapper around generators, but I don't think it will be useful or beautiful |
Sorry to interject something I have just been exposed to, and I can’t say whether it would help this particular use case, but it does handle generators and promises in a way I have yet to see.
https://effect.website/
…On Dec 29, 2023 at 2:34 PM -0500, Valerii Smirnov ***@***.***>, wrote:
I don't think this case should be handled. You have primitives for this case, if you want - you can write your own wrapper around generators, but I don't think it will be useful or beautiful
Maybe good solution will be eslint plugin that checks it, but it will have false positives with some object with value field.
I actually don't think you should use async functions with computed at all, there are resource for that cases. Signals is sync primitive, if you want async reactivity - you should use rxjs
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you are subscribed to this thread.Message ID: ***@***.***>
|
I'm starting a new React.js project, and was looking at replacing react's useEffect, etc. with Singals. But it seems when l populate the signal with the result of an async fetch statement and try to use the resulting signal to populate my component, nothing ever renders (unless I do a console log of the data for some strange reason). I look all over the place and have never seen an example of an async fetch call used to populate a signal. We do this all the time in React.js with useEffect. Does anyone know if this is possible with signals? If not then I guess I'll stick with react hooks. |
Hey there,
I am using Preact more and more since the publication of the Signals API and its a great and, especially in relation to regular state and context, easy way to build a reactive user
Interface.
In my newest project though I have reached an issue for which I am looking for a recommended way to solve it: Whats the best way to handle a computed signal when its callback method is asynchronous but you want to deal only with its resolved value?
Example:
The code above will result in
content
always being a Promise, but I want it to be the resolved value of the async function instead.So far I have solved this issue with something like this:
That solution does work, but seems a bit messy, especially because it can not be used directly in a class.
Is there a better solution to handle this or would it even be a reason for adding a new method to Signals like
asyncComputed()
(orcomputed.await()
)?Thank you very much for your time and I am looking forward to an interessting discussion.
Kind regards,
Happy Striker
The text was updated successfully, but these errors were encountered: