From 44126a33cf1d76efd38b95a7aab3ef84f6e0e667 Mon Sep 17 00:00:00 2001 From: Dominik Csapak Date: Wed, 13 Sep 2023 14:04:43 +0200 Subject: [PATCH] `wasm-bindgen-futures`: use `queueMicrotask` for next tick runs (#3203) but use the old `Promise.then` mechanism as a fallback. Cache the existance of `queueMicrotask` in the queue instance. Signed-off-by: Dominik Csapak --- CHANGELOG.md | 7 +++++++ crates/futures/src/queue.rs | 31 +++++++++++++++++++++++++++---- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f3b664e66a..6213af8b033 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -92,6 +92,13 @@ `#[repr(C)]` types. [#3595](https://github.com/rustwasm/wasm-bindgen/pull/3595) +* Use `queueMicrotask` in `wasm-bindgen-futures` for scheduling tasks on the next tick. + If that is not available, use the previous `Promise.then` mechanism as a fallback. + This should avoid quirks, like exceptions thrown get now properly reported + as normal exceptions rather than as rejected promises. + [#3611](https://github.com/rustwasm/wasm-bindgen/pull/3611) + [#2392](https://github.com/rustwasm/wasm-bindgen/issues/2392) + ### Fixed * Fixed bindings and comments for `Atomics.wait`. diff --git a/crates/futures/src/queue.rs b/crates/futures/src/queue.rs index 9a78571cd84..4d8e48800d2 100644 --- a/crates/futures/src/queue.rs +++ b/crates/futures/src/queue.rs @@ -4,6 +4,19 @@ use std::collections::VecDeque; use std::rc::Rc; use wasm_bindgen::prelude::*; +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen] + fn queueMicrotask(closure: &Closure); +} + +#[wasm_bindgen] +extern "C" { + type Global; + #[wasm_bindgen(method, getter, js_name = queueMicrotask)] + fn hasQueueMicrotask(this: &Global) -> JsValue; +} + struct QueueState { // The queue of Tasks which are to be run in order. In practice this is all the // synchronous work of futures, and each `Task` represents calling `poll` on @@ -42,17 +55,21 @@ pub(crate) struct Queue { state: Rc, promise: Promise, closure: Closure, + has_queue_microtask: bool, } impl Queue { // Schedule a task to run on the next tick pub(crate) fn schedule_task(&self, task: Rc) { self.state.tasks.borrow_mut().push_back(task); - // Note that we currently use a promise and a closure to do this, but - // eventually we should probably use something like `queueMicrotask`: - // https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/queueMicrotask + // Use queueMicrotask to execute as soon as possible. If it does not exist + // fall back to the promise resolution if !self.state.is_scheduled.replace(true) { - let _ = self.promise.then(&self.closure); + if self.has_queue_microtask { + queueMicrotask(&self.closure); + } else { + let _ = self.promise.then(&self.closure); + } } } // Append a task to the currently running queue, or schedule it @@ -70,6 +87,11 @@ impl Queue { tasks: RefCell::new(VecDeque::new()), }); + let has_queue_microtask = js_sys::global() + .unchecked_into::() + .hasQueueMicrotask() + .is_function(); + Self { promise: Promise::resolve(&JsValue::undefined()), @@ -82,6 +104,7 @@ impl Queue { }, state, + has_queue_microtask, } } }