Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wasm-bindgen-futures: use queueMicrotask for next tick runs (#3203) #3611

Merged
merged 3 commits into from
Sep 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@
`#[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)

### Fixed

* Fixed bindings and comments for `Atomics.wait`.
Expand Down
29 changes: 25 additions & 4 deletions crates/futures/src/queue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ use std::collections::VecDeque;
use std::rc::Rc;
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
extern "C" {
#[wasm_bindgen]
fn queueMicrotask(closure: &Closure<dyn FnMut(JsValue)>);

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
Expand Down Expand Up @@ -42,17 +53,21 @@ pub(crate) struct Queue {
state: Rc<QueueState>,
promise: Promise,
closure: Closure<dyn FnMut(JsValue)>,
has_queue_microtask: bool,
}

impl Queue {
// Schedule a task to run on the next tick
pub(crate) fn schedule_task(&self, task: Rc<crate::task::Task>) {
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
Expand All @@ -70,6 +85,11 @@ impl Queue {
tasks: RefCell::new(VecDeque::new()),
});

let has_queue_microtask = js_sys::global()
.unchecked_into::<Global>()
.hasQueueMicrotask()
.is_function();

Self {
promise: Promise::resolve(&JsValue::undefined()),

Expand All @@ -82,6 +102,7 @@ impl Queue {
},

state,
has_queue_microtask,
}
}
}
Expand Down