xilem_web: Add a `await_once` view, and a simple example `suspense` showing it in action.
This adds a view similar as xilem_web::concurrent::memoized_await with the difference, that it's not having any memoized data, and instead takes the app state as first parameter, the future is run just once.
This is demonstrated within a new suspense example, which shows, that this view could be used similar as something like Reacts Suspense. This doesn't necessarily mean, that there won't be a more specialized view for this use-case though.
It also shows a slightly more interesting use-case of Either (accessing the Class view for both p and h1).
Just tried this locally, and it seems like a nice addition!
I was able to rewrite this:
memoized_await(
(),
|()| async {
Api::default()
.packages()
.await
.unwrap()
.into_iter()
.collect::<Vec<(Hash, Manifest)>>()
},
|state: &mut State, output| state.packages = output,
),
memoized_await(
(),
|()| async { Api::default().handlers().await.unwrap() },
|state: &mut State, output| state.handlers = output,
),
Into:
await_once(
|_| async {
Api::default()
.packages()
.await
.unwrap()
.into_iter()
.collect::<Vec<(Hash, Manifest)>>()
},
|state: &mut State, output| state.packages = output,
),
await_once(
|_| async { Api::default().handlers().await.unwrap() },
|state: &mut State, output| state.handlers = output,
),
I like the name await_once, and it might be nice if memoized_await were renamed await_memoized to make their similarity slightly more obvious.
One thought, is it necessary to give await_once access to the initial state?
Instead of:
await_once(
|state| async { async_function(state).await },
|state: &mut State, output| state.foo = output,
),
Could it be:
await_once(
async_function(state), // since we're probably calling await_once in a context where `state` is in scope
|state: &mut State, output| state.foo = output,
),
And when constructing the future doesn't require access to the state at all:
await_once(
async_function(),
|state: &mut State, output| state.foo = output,
),
I like the name
await_once, and it might be nice ifmemoized_awaitwere renamedawait_memoizedto make their similarity slightly more obvious.
Yeah I think that makes sense.
Could it be:
I'm not sure, as the app_logic is evaluated quite often, so that async_function(state) is run multiple times, and may lead to weird/unexpected behavior, when it changes the state.
Regarding the state as initial argument, see this zulip topic for more thoughts, or in other words I'm not yet sure, but it may be interesting for the user to track some state, when this is evaluated first. Though the whole async story is still quite in flux I think.
I think the version directly accepting a future makes sense. It would need to move the fields it needs from state, but that's fine.
I was more thinking about having an "event" when the future is actually initialized, but as said I'm not sure about it, so having a future there directly maybe makes sense as well. Hmm I'm not sure about especially this init future event, I think it could certainly be useful, to really know when a future has started, but as noticed in the fetch example, also leads to more imperative code, not sure how we should handle these kind of "events" in general...
Another issue with having a future here directly is also, that it may lead to surprising behavior when the future itself changes, as it would not have any effect, as it would then be invoked in View::build.
Actually, thinking more about it, I think requiring a function/closure which takes the current state and returns a future is perhaps best for clarity, i.e., reinforcing that the closure will only be called once, as opposed to taking a future each time, but only evaluating that future once.
I was more thinking about having an "event" when the future is actually initialized, but as said I'm not sure about it, so having a future there directly maybe makes sense as well. Hmm I'm not sure about especially this init future event, I think it could certainly be useful, to really know when a future has started, but as noticed in the fetch example, also leads to more imperative code, not sure how we should handle these kind of "events" in general...
Another issue with having a future here directly is also, that it may lead to surprising behavior when the future itself changes, as it would not have any effect, as it would then be invoked in
View::build.
I have to admit, I'm still a bit confused about the Xilem approach, the feeling that everything in the world is a view. But maybe I still don't understand the approach. I mean, the probability that I have a large number of asynchronous tasks that don't have anything directly to do with the GUI at first, but actually only the effects of it, is very high, isn't it? Here you would be tempted to pack all asynchronous tasks into views, which is not really architecturally clean, is it?