I have a long running wasm function written in Rust which should be cancelable by the user (for example, by clicking a cancel button in the UI), that is, by a JavaScript event. My idea is to use a custom cancellation token like that:
#[wasm_bindgen]
pub struct JsCancellationToken {
token: Cell<bool>,
}
#[wasm_bindgen]
impl JsCancellationToken {
pub fn new() -> JsCancellationToken {
JsCancellationToken {
token: Cell::new(false),
}
}
pub fn cancel(&self) {
self.token.set(true)
}
pub async fn is_cancellation_requested(&self) -> bool {
handle_js_events().await;
self.token.get()
}
}
#[wasm_bindgen]
pub async fn long_running_function(ct: &JsCancellationToken) -> u32 {
while !ct.is_cancellation_requested().await {
...
}
...
}
The crucial detail is the (custom) handle_js_events
function which prevents long_running_function
from blocking the JavaScript event queue forever. My naive implementation of that function looks like that:
use js_sys::{Function, Promise};
use wasm_bindgen_futures::JsFuture;
#[wasm_bindgen]
extern "C" {
fn setTimeout(closure: &Function, millis: u32);
}
async fn handle_js_events() {
JsFuture::from(Promise::new(
&mut |resolve: Function, _reject: Function| {
setTimeout(&resolve, 0);
},
))
.await
.unwrap();
}
This is working, but it always feel hacky to use setTimeout(..., 0)
. Is there a more direct way to interrupt work for a moment and make JavaScript handle the latest events? Or is there an even better way to cancel a wasm function from the UI?