0

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?

Dune
  • 293
  • 1
  • 10
  • Not sure about interruption, but FWIW JS promises are *always* resolved asynchronously, so `Promise::resolve(...)` or immediately calling `resolve` should be sufficient to "yield". – Masklinn Nov 26 '22 at 21:37
  • That aside, if you want to run code concurrently a worker might be appropriate, and provides an asynchronous signaling queue (though likely somewhat more expensive than a simple token) – Masklinn Nov 26 '22 at 21:39
  • @Masklinn I tried using a worker, but it did not make any difference. It seems that without such a `handle_js_events` function, the long running function would also block the worker's event queue forever.. – Dune Nov 26 '22 at 21:41
  • @Masklinn Using `JsFuture::from(Promise::resolve(&JsValue::NULL)).await` instead of setTimeout don't seem to work either. The UI thread just freezes (not using a worker in this case). – Dune Nov 26 '22 at 21:49
  • @Masklinn Promise callbacks are asynchronous, but they're queued on the microtask queue, which will still block the UI by not running the event loop when infinitely resolving promises immediately. – Bergi Nov 26 '22 at 23:41

0 Answers0