Description
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.