Skip to content

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

Closed
@dragly

Description

@dragly

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions