DioxusLabs / DioxusLabs/dioxus
Improve ergonomics of async integration
- Dominant language
- Rust
- Stars
- 39.1k
- Forks
- 1.9k
- Avg merge
- 4d 10h
- Merged PRs (30d)
- 4
Description
## Feature Request
This is a spin-off from #4011 ("Async closure for "onclick": spawns into the aether").
Related issue: #3481
When interfacing with a rest api, and targeting web, there's really no other choice than using an async http client. This means lots of async integration in event handlers.
In general, this can work:
```rust
rsx! {
div {
onclick: move |_| {
async move {
// ...
}
}
}
}
```
However, it's not that ergonomic to use:
### 1. The "magic" async support works by returning a future from the event handler
This means there's no way to use an async block in the middle of the event handler. The entire "bottom half" (or the whole) of the event handler must be async.
### 2. There's (seemingly?) no way to .await the async block
This makes it extra difficult to make things happen in a certain order.
For example, I tried to make a button component that displays a spinner while the `.onclick` is running. Conceptually, something like this:
```rust
#[component]
pub fn SpinnerButton(onclick: EventHandler, running: Signal) -> Element {
rsx! {
button {
onclick: move |evt| {
running.set(true);
onclick.call(evt);
running.set(false);
}
}
}
}
```
This seems reasonable, no?
Well, if `onclick` is given as an `async` move block, it's wrapped in something that causes it to be spawned, but not awaited in a blocking-like way. The result is that `.call()` exits immediately, and so the `SpinnerButton` concept appears broken.
This is pretty surprising, and I haven't been able to find a workaround of any kind.
## Suggestion
In #4011, @ealmloff had this comment:
> This is interesting. Async event handler were originally added for just elements, but they were later expanded into components to make behavior more consistent.
>
> It seems reasonable to return a status object that implements IntoFuture so you could await the result.
>
> This would be a good issue to spin out from this thread.
>
> As a workaround, you can accept a closure that returns a boxed future explicitly with Callback>>
The workaround is certainly a neat idea, but it does come with some problems:
- The component would then *only* be able to accept async closures
- There's still no way to tell when the handler is done running
I think there's a design problem with the way async closures are handled right now.
In other words:
```rust
callback.call();
```
Should *wait* until the closure is done running, no matter if it's sync or async.
I'm not sure how exactly to implement this, but that would solve this entire issue.
Anyone who explicitly wants to spawn a background task, can already do so with the `spawn()` helper.
I can't really see any downside to the blocking behavior, but there's plenty of advantages.
Thoughts?
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.