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

A future passed to future_to_promise is not invoked if this happens in a JS callback #2246

Closed
dragly opened this issue Jul 19, 2020 · 1 comment
Labels

Comments

@dragly
Copy link

dragly commented Jul 19, 2020

Describe the Bug

The wasm_bindgen_futures::future_to_promise function does not appear to schedule a future if it is called in a closure used in a JS callback.

I suspect this might be related to the rational for adding must_use to js_sys::Promise (#1927), but as mentioned by @Pauan in that thread I also thought Promises would be eagerly evaluated. In my case, I expected them to be evaluated regardless of whether they are created in a callback.

Steps to Reproduce

The following code reproduces the issue:

use wasm_bindgen::JsValue;

#[wasm_bindgen(start)]
pub fn start() -> Result<(), JsValue> {
    wasm_bindgen_futures::future_to_promise(async {
        web_sys::console::log_1(&JsValue::from("Future works"));
        Ok::<JsValue, JsValue>(JsValue::undefined())
    });

    let mouse_down = Closure::wrap(Box::new(|event: &web_sys::Event| {
        web_sys::console::log_1(&JsValue::from("Mouse down sync"));
        wasm_bindgen_futures::future_to_promise(async {
            web_sys::console::log_1(&JsValue::from("Mouse down async"));
            Ok::<JsValue, JsValue>(JsValue::undefined())
        });
    }) as Box<dyn FnMut(&web_sys::Event)>);

    web_sys::window()
        .expect("Should have window")
        .set_onmousedown(Some(JsCast::unchecked_from_js_ref(mouse_down.as_ref())));

    mouse_down.forget();

    Ok(())
}

Expected Behavior

I would expect that "Future works" is printed immediately, while "Mouse down sync" and "Mouse down async" are printed when a mouse button is clicked.

Actual Behavior

While "Future works" is printed immediately, only "Mouse down sync" is printed when the mouse is clicked.

Additional Context

What I am trying to create is an async callback function, which would allow me to call JsPromise::from(...).await. In particular, I am trying to parse a file that is dropped by the user onto the window. I am able to do this with the following code:

    let drop_callback = Closure::wrap(Box::new(|event: &web_sys::Event| {
        event.prevent_default();
        let drag_event: &web_sys::DragEvent = JsCast::unchecked_from_js_ref(event);
        match drag_event.data_transfer() {
            None => {}
            Some(data_transfer) => match data_transfer.files() {
                None => {}
                Some(files) => {
                    for i in 0..files.length() {
                        if let Some(file) = files.item(i) {
                            let cb = Closure::wrap(Box::new(|text: JsValue| {
                                let text_string: String = text.as_string().unwrap();
                                web_sys::console::log_1(&JsValue::from(format!(
                                            "Contents: {}",
                                            text_string
                                )));
                            }) as Box<dyn FnMut(JsValue)>);
                            file.text().then(&cb);
                            cb.forget();
                        }
                    }
                }
            },
        }
    }) as Box<dyn FnMut(&web_sys::Event)>);

But I wish to write it using async/await instead of file.text.then(&cb). Ideally using an async closure, but that does not seem to work since Closure::wrap appears to expect an FnMut, which is why I started dabbling with async blocks instead.

@dragly dragly added the bug label Jul 19, 2020
@dragly
Copy link
Author

dragly commented Jul 19, 2020

Ooops... turns out I was wrong about the above example. That actually works. The issue originates from using winits event loop in a spawn_local. I had this in there because I started out with the hello-triangle example from wgpu-rs. The following does not work, for instance:

use wasm_bindgen::prelude::*;
use wasm_bindgen::{JsCast, JsValue};
use winit::{
    dpi::PhysicalPosition,
    event::{ElementState, Event, MouseButton, WindowEvent},
    event_loop::{ControlFlow, EventLoop, EventLoopProxy},
};

#[wasm_bindgen(start)]
pub fn start() -> Result<(), JsValue> {
    let event_loop = EventLoop::new();
    let mouse_down = Closure::wrap(Box::new(|event: &web_sys::Event| {
        web_sys::console::log_1(&JsValue::from("Mouse down sync"));
        wasm_bindgen_futures::future_to_promise(async {
            web_sys::console::log_1(&JsValue::from("Mouse down async"));
            Ok::<JsValue, JsValue>(JsValue::undefined())
        });
    }) as Box<dyn FnMut(&web_sys::Event)>);

    web_sys::window()
        .expect("Should have window")
        .set_onmousedown(Some(JsCast::unchecked_from_js_ref(mouse_down.as_ref())));

    mouse_down.forget();

    wasm_bindgen_futures::spawn_local(async {
        event_loop.run(move |_, _, _| {});
    });
    Ok(())
}

However, moving the event loop out of the call to spawn_local works:

use wasm_bindgen::prelude::*;
use wasm_bindgen::{JsCast, JsValue};
use winit::{
    dpi::PhysicalPosition,
    event::{ElementState, Event, MouseButton, WindowEvent},
    event_loop::{ControlFlow, EventLoop, EventLoopProxy},
};

#[wasm_bindgen(start)]
pub fn start() -> Result<(), JsValue> {
    let event_loop = EventLoop::new();
    let mouse_down = Closure::wrap(Box::new(|event: &web_sys::Event| {
        web_sys::console::log_1(&JsValue::from("Mouse down sync"));
        wasm_bindgen_futures::future_to_promise(async {
            web_sys::console::log_1(&JsValue::from("Mouse down async"));
            Ok::<JsValue, JsValue>(JsValue::undefined())
        });
    }) as Box<dyn FnMut(&web_sys::Event)>);

    web_sys::window()
        .expect("Should have window")
        .set_onmousedown(Some(JsCast::unchecked_from_js_ref(mouse_down.as_ref())));

    mouse_down.forget();

    wasm_bindgen_futures::spawn_local(async {
        // do stuff
    });
    event_loop.run(move |_, _, _| {});
    Ok(())
}

I guess this is not too surprising. The event loop might be blocking some types of tasks from getting scheduled.

I will close this now and open an issue over in wgpu-rs instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant