YoloDev / rstml-component

MIT License
4 stars 2 forks source link

RFC async functional components using async-std #40

Closed davidon-top closed 2 months ago

davidon-top commented 4 months ago

Hi, i have created a proof of concept here of how async functional components could work using async-std since its easy to have sync->async->sync->async call stack and also can be used even if the app is using tokio (tho only one way async components would be able to be called from a tokio async (or even a sync) function but inside async components only async-std compatible things would be able to be called)

It would proboably be behind a feature flag since its an extra dependency (and an extra async runtime if using tokio). For projects using async-std or if its ok to have 2 runtimes in the project this would be a great feature imo.

I'm happy to make a pr if this is a feature you want.

Alxandr commented 4 months ago

I don't like the solution of using block_on. I would much rather we add a AsyncHtmlComponent trait async in that case. That being said - given that there is currently no support for streaming the response of a HtmlComponent out I don't see how async html components would be beneficial.

davidon-top commented 4 months ago

the benefit of using this is that i can fetch required data directly inside the needed component and not have to pass down data from the top component all the way down I understand that i could just block_on everything i need to do inside a component but this is just a nice qol feature Other way to make this easier to deal with is something like react's Context allowing me to setup some state at the top component and retrieve that state in any nested component tho this would be harder to implement then just one block_on

Alxandr commented 4 months ago

I don't like the idea of automatically add block_on at least. Though I think there is some design space for async components available that we could definitely think about. The big issue as always is closures.

davidon-top commented 4 months ago

Ill try to make another poc implementing async components probably with an async trait or something else i come up with. But i don't have a lot of free time these days so for now ill close this issue (eta start of june for the poc).

Alxandr commented 4 months ago

Don't worry about it. Personally, I don't think it's a (as of yet) solvable problem. When I started this library I tried really hard to be able to write to a generic writer (and extending that to an async writer would make a ton of sense) - but I was not able to get it to work since you can't have generic lambdas.

davidon-top commented 3 months ago

I've taken a look at the code more in depth. What if we make a nightly_async feature flag and just have something like:

async_html! {
    <Component.await></Component>
}

with a custom parser for async components which will expand to something like

async move |__html: &mut ::rstml_component::HtmlFormatter| -> ::std::fmt::Result {
    .......
    __html.write_async_content(Component {}).await?;
    .......
}

and write_async_content would just call an async_trait's fmt(formatter).await just as write_content does

I might be completely wrong and missing something but this should be doable right? (i haven't worked with async_closures much either so its possible im missing something there too)

Alxandr commented 3 months ago

Hmm. That's an interesting proposal... I'm not sure we can use the <Component.await> syntax, but there's definitely some exploration possible at least. I also wonder if we could make this without having to make a second html macro, and I would have to look into the other macros for this crate a bit.

It would not have to be nightly only however. And I would prefer it not be.

davidon-top commented 3 months ago

not sure we can use the

we just need something to tell the macro that its an async component it can look any way we like

without having to make a second html macro

probably, during parsing we could save/return some metadata to tell whether any components used inside is async and add the async/await token respectively

It would not have to be nightly

there may be a workarounds to not require any nightly #![feature()] flags. will have to look into that one

I am making a POC to see whats technically viable (i haven't reached the parsing of so i don't know yet how we could do it). after that i can clean things up and make a PR

Alxandr commented 3 months ago

I recomend poc it just assuming all components are async, then figure out the await syntax later.

davidon-top commented 3 months ago

I've created a github project to track progress on the poc (until it becomes a pr). If you wish to add anything i can add you as an admin to that project if you wish.

Alxandr commented 3 months ago

I don't appear to have permissions to do anything there xD - but the thing I feel like I should comment on (just by a cursory look) is that you don't need async closures. Just closures that returns futures (or IntoHtml or some other trait that we use/make).

davidon-top commented 3 months ago

impl Trait is not allowed in closure return types; So impl Future wouldn't work

Alxandr commented 3 months ago

I know, but you can do something like:

where
  F: FnOnce(&'a AsyncHtmlFormatter) -> Fut,
  Fut: Future<Output = ()> + 'a
davidon-top commented 3 months ago

status update: Running into one roadblock after another, progress is slow and hard.

Alxandr commented 3 months ago

Yep - I can't say I expected anything else. I initially wanted to have a generic HtmlFormatter so I could have one that writes indented and one that writes a single line, but I eventually gave up.

davidon-top commented 2 months ago

Giving up on this. I found a workaround using global_async_executor crate and still using tokio. + i created leptos_reactive_axum to be able to create a context and access the data anyware in a function called by a handler and be able to run parts extractors anywhere (using the context).