Skip to content
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

Consider handling async service effects through futures #364

Open
blueridanus opened this issue Aug 16, 2018 · 14 comments
Open

Consider handling async service effects through futures #364

blueridanus opened this issue Aug 16, 2018 · 14 comments
Labels
A-yew Area: The main yew crate feature-request A feature request

Comments

@blueridanus
Copy link

Description

I'm submitting a feature request.

As it currently stands, asynchronous tasks are done through functions exposed by services, taking user-supplied callbacks which handle effects upon completion.

This approach is not very idiomatic. Besides, callback-structured code tends to be brittle and compose poorly.

The underlying Web APIs make use of promises. With the new wasm-bindgen-futures, it might be plausible to switch to using futures instead, without much implementation hassle. This would integrate well with the rest of the Rust async ecosystem.

@blueridanus
Copy link
Author

Hi @deniskolodin!

I'd like to work on this one, but I'm a bit unfamiliar with the code.
Perhaps you could give me some directions on gitter? If possible, just ping me and I'll reply.

Thanks.

@liamcurry
Copy link

I believe this would be difficult to implement without rewriting Yew to use wasm-bindgen instead of stdweb. Maybe it would be worth it to make that switch though? @deniskolodin thoughts?

@extraymond
Copy link

extraymond commented Aug 3, 2019

Hi! Since yew now supports wasm-bindgen, it's possible to use async await alongside yew.

Here's a little snippet that I've created to let rust handle the async task via futures.

use wasm_timer::Delay;
use std::sync::Arc;
use futures::lock::Mutex;
use wasm_bindgen_futures::futures_0_3::{future_to_promise, spawn_local, JsFuture};

//
// typical yew setup, I created a minimal model that act as an counter.
//

#[wasm_bindgen]
pub fn run_yew_fut() -> Result<(), JsValue> {
    spawn_local(async {
        let scope = make_scope();
        let guarded_scope = Arc::new(Mutex::new(scope));
        let fut1 = call_scope_later(guarded_scope.clone());
        let fut2 = log_later();

        let chained = futures::future::join(fut1, fut2);
        let resolved = chained.await;
    });
    Ok(())
}

fn make_scope() -> yew::html::Scope<Model> {
    yew::initialize();
    let app: App<Model> = App::new();
    app.mount_to_body()
}

async fn log_later() {
    console_log::init_with_level(Level::Debug).unwrap();
    let mut counts: usize = 0;
    loop {
        Delay::new(Duration::from_secs(1)).await.unwrap();
        counts += 1;
        info!("logged: {} times", counts);
    }
}

async fn call_scope_later(scope: Arc<Mutex<yew::html::Scope<Model>>>) {
    let mut inner = scope.lock().await;
    let mut counter = 0;
    while counter < 10 {
        Delay::new(Duration::from_secs(1)).await.unwrap();
        inner.send_message(Msg::DoIt);
        counter += 1;
    }
}

Although I'm not sure who is handling the UI loop since I didn't execute yew::run_loop(), but here's what I've got.
Maybe some work flow might benefit more from using futures instead of js promises.

out

You can see that the two async funtion are not blocking each other, either prevents the user from interacting with the component.

Noted that wasm didn't have mature threading support right now, so I have to use local executor. Maybe after we have threading in wasm we can make this into a threaded UI library.

@extraymond
Copy link

It seems that wasm-bindgen >= 0.2.48 is required to support async-await.
Currently, yew 0.8 set the dependency of wasm-bindgen=0.2.42, so if you want to try async/await, you might need to stick to 0.7 for now.

@jstarry
Copy link
Member

jstarry commented Aug 12, 2019

Thanks for calling this out @extraymond, later versions of wasm-bindgen were breaking stdweb so I fixed the version for now. Created an issue here: #586

@extraymond
Copy link

Sure! Looking forward to future releases. It's getting better and better every releases.

@extraymond
Copy link

rustasync team had released a http request library surf, which can be used under wasm and async. Seems like the easiest way to integrate fetch as future.

@liamcurry
Copy link

The only drawback to surf is it doesn't support aborting/dropping requests.
http-rs/surf#26 (comment)

@extraymond
Copy link

extraymond commented Aug 26, 2019

Thx for the update. I believe futures can be aborted by wrapping it in a Abortable.

use futures::future::{AbortHandle, Abortable};

fn create_abort_handle() -> AbortHandle  {
    let (abort_handle, abort_registration) = AbortHandle::new_pair();
    let req_futures = create_request(some_url);
    let wrapped_req = Abortable::new(req_futures, abort_registration);
    spawn_local(wrapped_req);
    abort_handle
}

fn main() {
    let abort_handle = create_abort_handle();
    abort_handle.abort();
}

I tried AbortHandle to cancel tasks, and it worked right away. Even wrapping promise to a JsFuture works.

However I'm not sure if this will stop the browser from requesting, or just ignoring the incoming response into the wasm program. I think the latter case is ideal enough though.

@chrysn
Copy link

chrysn commented Dec 2, 2020

Having async operations available would also help in dealing with heavily asynchronous APIs accessed through web-sys. In the tests I'm currently doing that appears to work well, and I'm using code like

async fn discover() -> Result<web_sys::BluetoothRemoteGattServer, String> {
    [ lots of web-sys calls through JsFuture ]

    // This still has to get better ergonomics, but one thing at a time…
    let connection = gatt.connect();
    let connection = wasm_bindgen_futures::JsFuture::from(connection).await
        .map_err(|e| format!("Connect failed {:?}", e))?;
    let connection = web_sys::BluetoothRemoteGattServer::from(connection);

    Ok(connection)
}

async fn wrap<F: std::future::Future>(f: F, done_cb: yew::Callback<F::Output>) {
    done_cb.emit(f.await);
}

impl Component for BleBridge {
    [...]
    fn update(&mut self, msg: Self::Message) -> ShouldRender {
        match msg {
            Msg::GattConnectClicked => {
                wasm_bindgen_futures::spawn_local(wrap(discover(),
                self.link.callback(|e| Msg::GattConnectDone(e))));
            }
            Msg::GattConnectDone(d) => {
                match d {
                    Ok(d) => { info!("Got {:?}", d); }
                    Err(d) => { info!("Error: {:?}", d); }
                }
            }
        }
        false
    }
}

For some applications, it may suffice to document that this works (provided it's correct w/rt when emit may be called). This shouldn't stop any deeper integration of async patterns into yew, but may save current users the despair of thinking that there's no futures / async support around because none of it is mentioned in the documentation.

@teymour-aldridge
Copy link
Contributor

I really like this solution (it feels satisfying as I read the short code excerpt) 🙃.

@tieje
Copy link
Contributor

tieje commented Feb 21, 2023

@hamza1311 , if this has not been solved, you can hand it off to me.

@dricair
Copy link

dricair commented Mar 4, 2023

If someone is reading this page like I did, the example above does not fully work anymore, although the concepts are still accurate.

But more support has been added in Yew and we have an example: Futures

@Ekleog
Copy link
Contributor

Ekleog commented Feb 20, 2024

FWIW, I just opened #3609 that should make things in this domain better with upstream yew, if it lands :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-yew Area: The main yew crate feature-request A feature request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

10 participants