wasm_bindgen_futures/
lib.rs

1//! Converting between JavaScript `Promise`s to Rust `Future`s.
2//!
3//! This crate provides a bridge for working with JavaScript `Promise` types as
4//! a Rust `Future`, and similarly contains utilities to turn a rust `Future`
5//! into a JavaScript `Promise`. This can be useful when working with
6//! asynchronous or otherwise blocking work in Rust (wasm), and provides the
7//! ability to interoperate with JavaScript events and JavaScript I/O
8//! primitives.
9//!
10//! There are three main interfaces in this crate currently:
11//!
12//! 1. [**`JsFuture`**](./struct.JsFuture.html)
13//!
14//!    A type that is constructed with a `Promise` and can then be used as a
15//!    `Future<Output = Result<JsValue, JsValue>>`. This Rust future will resolve
16//!    or reject with the value coming out of the `Promise`.
17//!
18//! 2. [**`future_to_promise`**](./fn.future_to_promise.html)
19//!
20//!    Converts a Rust `Future<Output = Result<JsValue, JsValue>>` into a
21//!    JavaScript `Promise`. The future's result will translate to either a
22//!    resolved or rejected `Promise` in JavaScript.
23//!
24//! 3. [**`spawn_local`**](./fn.spawn_local.html)
25//!
26//!    Spawns a `Future<Output = ()>` on the current thread. This is the
27//!    best way to run a `Future` in Rust without sending it to JavaScript.
28//!
29//! These three items should provide enough of a bridge to interoperate the two
30//! systems and make sure that Rust/JavaScript can work together with
31//! asynchronous and I/O work.
32
33#![cfg_attr(target_feature = "atomics", feature(stdarch_wasm_atomic_wait))]
34#![deny(missing_docs)]
35#![cfg_attr(docsrs, feature(doc_cfg))]
36
37use js_sys::Promise;
38use std::cell::RefCell;
39use std::fmt;
40use std::future::Future;
41use std::pin::Pin;
42use std::rc::Rc;
43use std::task::{Context, Poll, Waker};
44use wasm_bindgen::prelude::*;
45
46mod queue;
47#[cfg_attr(docsrs, doc(cfg(feature = "futures-core-03-stream")))]
48#[cfg(feature = "futures-core-03-stream")]
49pub mod stream;
50
51pub use js_sys;
52pub use wasm_bindgen;
53
54mod task {
55    use cfg_if::cfg_if;
56
57    cfg_if! {
58        if #[cfg(target_feature = "atomics")] {
59            mod wait_async_polyfill;
60            mod multithread;
61            pub(crate) use multithread::*;
62
63        } else {
64            mod singlethread;
65            pub(crate) use singlethread::*;
66         }
67    }
68}
69
70/// Runs a Rust `Future` on the current thread.
71///
72/// The `future` must be `'static` because it will be scheduled
73/// to run in the background and cannot contain any stack references.
74///
75/// The `future` will always be run on the next microtask tick even if it
76/// immediately returns `Poll::Ready`.
77///
78/// # Panics
79///
80/// This function has the same panic behavior as `future_to_promise`.
81#[inline]
82pub fn spawn_local<F>(future: F)
83where
84    F: Future<Output = ()> + 'static,
85{
86    task::Task::spawn(Box::pin(future));
87}
88
89struct Inner {
90    result: Option<Result<JsValue, JsValue>>,
91    task: Option<Waker>,
92    callbacks: Option<(Closure<dyn FnMut(JsValue)>, Closure<dyn FnMut(JsValue)>)>,
93}
94
95/// A Rust `Future` backed by a JavaScript `Promise`.
96///
97/// This type is constructed with a JavaScript `Promise` object and translates
98/// it to a Rust `Future`. This type implements the `Future` trait from the
99/// `futures` crate and will either succeed or fail depending on what happens
100/// with the JavaScript `Promise`.
101///
102/// Currently this type is constructed with `JsFuture::from`.
103pub struct JsFuture {
104    inner: Rc<RefCell<Inner>>,
105}
106
107impl fmt::Debug for JsFuture {
108    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
109        write!(f, "JsFuture {{ ... }}")
110    }
111}
112
113impl From<Promise> for JsFuture {
114    fn from(js: Promise) -> JsFuture {
115        // Use the `then` method to schedule two callbacks, one for the
116        // resolved value and one for the rejected value. We're currently
117        // assuming that JS engines will unconditionally invoke precisely one of
118        // these callbacks, no matter what.
119        //
120        // Ideally we'd have a way to cancel the callbacks getting invoked and
121        // free up state ourselves when this `JsFuture` is dropped. We don't
122        // have that, though, and one of the callbacks is likely always going to
123        // be invoked.
124        //
125        // As a result we need to make sure that no matter when the callbacks
126        // are invoked they are valid to be called at any time, which means they
127        // have to be self-contained. Through the `Closure::once` and some
128        // `Rc`-trickery we can arrange for both instances of `Closure`, and the
129        // `Rc`, to all be destroyed once the first one is called.
130        let state = Rc::new(RefCell::new(Inner {
131            result: None,
132            task: None,
133            callbacks: None,
134        }));
135
136        fn finish(state: &RefCell<Inner>, val: Result<JsValue, JsValue>) {
137            let task = {
138                let mut state = state.borrow_mut();
139                debug_assert!(state.callbacks.is_some());
140                debug_assert!(state.result.is_none());
141
142                // First up drop our closures as they'll never be invoked again and
143                // this is our chance to clean up their state.
144                drop(state.callbacks.take());
145
146                // Next, store the value into the internal state.
147                state.result = Some(val);
148                state.task.take()
149            };
150
151            // And then finally if any task was waiting on the value wake it up and
152            // let them know it's there.
153            if let Some(task) = task {
154                task.wake()
155            }
156        }
157
158        let resolve = {
159            let state = state.clone();
160            Closure::once(move |val| finish(&state, Ok(val)))
161        };
162
163        let reject = {
164            let state = state.clone();
165            Closure::once(move |val| finish(&state, Err(val)))
166        };
167
168        let _ = js.then2(&resolve, &reject);
169
170        state.borrow_mut().callbacks = Some((resolve, reject));
171
172        JsFuture { inner: state }
173    }
174}
175
176impl Future for JsFuture {
177    type Output = Result<JsValue, JsValue>;
178
179    fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
180        let mut inner = self.inner.borrow_mut();
181
182        // If our value has come in then we return it...
183        if let Some(val) = inner.result.take() {
184            return Poll::Ready(val);
185        }
186
187        // ... otherwise we arrange ourselves to get woken up once the value
188        // does come in
189        inner.task = Some(cx.waker().clone());
190        Poll::Pending
191    }
192}
193
194/// Converts a Rust `Future` into a JavaScript `Promise`.
195///
196/// This function will take any future in Rust and schedule it to be executed,
197/// returning a JavaScript `Promise` which can then be passed to JavaScript.
198///
199/// The `future` must be `'static` because it will be scheduled to run in the
200/// background and cannot contain any stack references.
201///
202/// The returned `Promise` will be resolved or rejected when the future completes,
203/// depending on whether it finishes with `Ok` or `Err`.
204///
205/// # Panics
206///
207/// Note that in wasm panics are currently translated to aborts, but "abort" in
208/// this case means that a JavaScript exception is thrown. The wasm module is
209/// still usable (likely erroneously) after Rust panics.
210///
211/// If the `future` provided panics then the returned `Promise` **will not
212/// resolve**. Instead it will be a leaked promise. This is an unfortunate
213/// limitation of wasm currently that's hoped to be fixed one day!
214pub fn future_to_promise<F>(future: F) -> Promise
215where
216    F: Future<Output = Result<JsValue, JsValue>> + 'static,
217{
218    let mut future = Some(future);
219
220    Promise::new(&mut |resolve, reject| {
221        let future = future.take().unwrap_throw();
222
223        spawn_local(async move {
224            match future.await {
225                Ok(val) => {
226                    resolve.call1(&JsValue::undefined(), &val).unwrap_throw();
227                }
228                Err(val) => {
229                    reject.call1(&JsValue::undefined(), &val).unwrap_throw();
230                }
231            }
232        });
233    })
234}