mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-04-29 23:52:16 +00:00
228 lines
7.7 KiB
Rust
228 lines
7.7 KiB
Rust
//! Converting between JavaScript `Promise`s to Rust `Future`s.
|
|
//!
|
|
//! This crate provides a bridge for working with JavaScript `Promise` types as
|
|
//! a Rust `Future`, and similarly contains utilities to turn a rust `Future`
|
|
//! into a JavaScript `Promise`. This can be useful when working with
|
|
//! asynchronous or otherwise blocking work in Rust (wasm), and provides the
|
|
//! ability to interoperate with JavaScript events and JavaScript I/O
|
|
//! primitives.
|
|
//!
|
|
//! There are three main interfaces in this crate currently:
|
|
//!
|
|
//! 1. [**`JsFuture`**](./struct.JsFuture.html)
|
|
//!
|
|
//! A type that is constructed with a `Promise` and can then be used as a
|
|
//! `Future<Output = Result<JsValue, JsValue>>`. This Rust future will resolve
|
|
//! or reject with the value coming out of the `Promise`.
|
|
//!
|
|
//! 2. [**`future_to_promise`**](./fn.future_to_promise.html)
|
|
//!
|
|
//! Converts a Rust `Future<Output = Result<JsValue, JsValue>>` into a
|
|
//! JavaScript `Promise`. The future's result will translate to either a
|
|
//! resolved or rejected `Promise` in JavaScript.
|
|
//!
|
|
//! 3. [**`spawn_local`**](./fn.spawn_local.html)
|
|
//!
|
|
//! Spawns a `Future<Output = ()>` on the current thread. This is the
|
|
//! best way to run a `Future` in Rust without sending it to JavaScript.
|
|
//!
|
|
//! These three items should provide enough of a bridge to interoperate the two
|
|
//! systems and make sure that Rust/JavaScript can work together with
|
|
//! asynchronous and I/O work.
|
|
|
|
#![cfg_attr(target_feature = "atomics", feature(stdsimd))]
|
|
#![deny(missing_docs)]
|
|
|
|
use js_sys::Promise;
|
|
use std::cell::RefCell;
|
|
use std::fmt;
|
|
use std::future::Future;
|
|
use std::pin::Pin;
|
|
use std::rc::Rc;
|
|
use std::task::{Context, Poll, Waker};
|
|
use wasm_bindgen::prelude::*;
|
|
|
|
mod queue;
|
|
|
|
mod task {
|
|
use cfg_if::cfg_if;
|
|
|
|
cfg_if! {
|
|
if #[cfg(target_feature = "atomics")] {
|
|
mod wait_async_polyfill;
|
|
mod multithread;
|
|
pub(crate) use multithread::*;
|
|
|
|
} else {
|
|
mod singlethread;
|
|
pub(crate) use singlethread::*;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Runs a Rust `Future` on the current thread.
|
|
///
|
|
/// The `future` must be `'static` because it will be scheduled
|
|
/// to run in the background and cannot contain any stack references.
|
|
///
|
|
/// The `future` will always be run on the next microtask tick even if it
|
|
/// immediately returns `Poll::Ready`.
|
|
///
|
|
/// # Panics
|
|
///
|
|
/// This function has the same panic behavior as `future_to_promise`.
|
|
#[inline]
|
|
pub fn spawn_local<F>(future: F)
|
|
where
|
|
F: Future<Output = ()> + 'static,
|
|
{
|
|
task::Task::spawn(Box::pin(future));
|
|
}
|
|
|
|
struct Inner {
|
|
result: Option<Result<JsValue, JsValue>>,
|
|
task: Option<Waker>,
|
|
callbacks: Option<(Closure<dyn FnMut(JsValue)>, Closure<dyn FnMut(JsValue)>)>,
|
|
}
|
|
|
|
/// A Rust `Future` backed by a JavaScript `Promise`.
|
|
///
|
|
/// This type is constructed with a JavaScript `Promise` object and translates
|
|
/// it to a Rust `Future`. This type implements the `Future` trait from the
|
|
/// `futures` crate and will either succeed or fail depending on what happens
|
|
/// with the JavaScript `Promise`.
|
|
///
|
|
/// Currently this type is constructed with `JsFuture::from`.
|
|
pub struct JsFuture {
|
|
inner: Rc<RefCell<Inner>>,
|
|
}
|
|
|
|
impl fmt::Debug for JsFuture {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(f, "JsFuture {{ ... }}")
|
|
}
|
|
}
|
|
|
|
impl From<Promise> for JsFuture {
|
|
fn from(js: Promise) -> JsFuture {
|
|
// Use the `then` method to schedule two callbacks, one for the
|
|
// resolved value and one for the rejected value. We're currently
|
|
// assuming that JS engines will unconditionally invoke precisely one of
|
|
// these callbacks, no matter what.
|
|
//
|
|
// Ideally we'd have a way to cancel the callbacks getting invoked and
|
|
// free up state ourselves when this `JsFuture` is dropped. We don't
|
|
// have that, though, and one of the callbacks is likely always going to
|
|
// be invoked.
|
|
//
|
|
// As a result we need to make sure that no matter when the callbacks
|
|
// are invoked they are valid to be called at any time, which means they
|
|
// have to be self-contained. Through the `Closure::once` and some
|
|
// `Rc`-trickery we can arrange for both instances of `Closure`, and the
|
|
// `Rc`, to all be destroyed once the first one is called.
|
|
let state = Rc::new(RefCell::new(Inner {
|
|
result: None,
|
|
task: None,
|
|
callbacks: None,
|
|
}));
|
|
|
|
fn finish(state: &RefCell<Inner>, val: Result<JsValue, JsValue>) {
|
|
let task = {
|
|
let mut state = state.borrow_mut();
|
|
debug_assert!(state.callbacks.is_some());
|
|
debug_assert!(state.result.is_none());
|
|
|
|
// First up drop our closures as they'll never be invoked again and
|
|
// this is our chance to clean up their state.
|
|
drop(state.callbacks.take());
|
|
|
|
// Next, store the value into the internal state.
|
|
state.result = Some(val);
|
|
state.task.take()
|
|
};
|
|
|
|
// And then finally if any task was waiting on the value wake it up and
|
|
// let them know it's there.
|
|
if let Some(task) = task {
|
|
task.wake()
|
|
}
|
|
}
|
|
|
|
let resolve = {
|
|
let state = state.clone();
|
|
Closure::once(move |val| finish(&state, Ok(val)))
|
|
};
|
|
|
|
let reject = {
|
|
let state = state.clone();
|
|
Closure::once(move |val| finish(&state, Err(val)))
|
|
};
|
|
|
|
let _ = js.then2(&resolve, &reject);
|
|
|
|
state.borrow_mut().callbacks = Some((resolve, reject));
|
|
|
|
JsFuture { inner: state }
|
|
}
|
|
}
|
|
|
|
impl Future for JsFuture {
|
|
type Output = Result<JsValue, JsValue>;
|
|
|
|
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
|
|
let mut inner = self.inner.borrow_mut();
|
|
|
|
// If our value has come in then we return it...
|
|
if let Some(val) = inner.result.take() {
|
|
return Poll::Ready(val);
|
|
}
|
|
|
|
// ... otherwise we arrange ourselves to get woken up once the value
|
|
// does come in
|
|
inner.task = Some(cx.waker().clone());
|
|
Poll::Pending
|
|
}
|
|
}
|
|
|
|
/// Converts a Rust `Future` into a JavaScript `Promise`.
|
|
///
|
|
/// This function will take any future in Rust and schedule it to be executed,
|
|
/// returning a JavaScript `Promise` which can then be passed to JavaScript.
|
|
///
|
|
/// The `future` must be `'static` because it will be scheduled to run in the
|
|
/// background and cannot contain any stack references.
|
|
///
|
|
/// The returned `Promise` will be resolved or rejected when the future completes,
|
|
/// depending on whether it finishes with `Ok` or `Err`.
|
|
///
|
|
/// # Panics
|
|
///
|
|
/// Note that in wasm panics are currently translated to aborts, but "abort" in
|
|
/// this case means that a JavaScript exception is thrown. The wasm module is
|
|
/// still usable (likely erroneously) after Rust panics.
|
|
///
|
|
/// If the `future` provided panics then the returned `Promise` **will not
|
|
/// resolve**. Instead it will be a leaked promise. This is an unfortunate
|
|
/// limitation of wasm currently that's hoped to be fixed one day!
|
|
pub fn future_to_promise<F>(future: F) -> Promise
|
|
where
|
|
F: Future<Output = Result<JsValue, JsValue>> + 'static,
|
|
{
|
|
let mut future = Some(future);
|
|
|
|
Promise::new(&mut |resolve, reject| {
|
|
let future = future.take().unwrap_throw();
|
|
|
|
spawn_local(async move {
|
|
match future.await {
|
|
Ok(val) => {
|
|
resolve.call1(&JsValue::undefined(), &val).unwrap_throw();
|
|
}
|
|
Err(val) => {
|
|
reject.call1(&JsValue::undefined(), &val).unwrap_throw();
|
|
}
|
|
}
|
|
});
|
|
})
|
|
}
|