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(not(feature = "std"), no_std)]
34#![cfg_attr(
35    target_feature = "atomics",
36    feature(thread_local, stdarch_wasm_atomic_wait)
37)]
38#![deny(missing_docs)]
39#![cfg_attr(docsrs, feature(doc_cfg))]
40
41extern crate alloc;
42
43use alloc::rc::Rc;
44use core::cell::RefCell;
45use core::fmt;
46use core::future::Future;
47use core::pin::Pin;
48use core::task::{Context, Poll, Waker};
49#[cfg(all(target_arch = "wasm32", feature = "std", panic = "unwind"))]
50use futures_util::FutureExt;
51use js_sys::Promise;
52#[cfg(all(target_arch = "wasm32", feature = "std", panic = "unwind"))]
53use wasm_bindgen::__rt::panic_to_panic_error;
54use wasm_bindgen::prelude::*;
55
56mod queue;
57#[cfg_attr(docsrs, doc(cfg(feature = "futures-core-03-stream")))]
58#[cfg(feature = "futures-core-03-stream")]
59pub mod stream;
60
61pub use js_sys;
62pub use wasm_bindgen;
63
64mod task {
65    use cfg_if::cfg_if;
66
67    cfg_if! {
68        if #[cfg(target_feature = "atomics")] {
69            mod wait_async_polyfill;
70            mod multithread;
71            pub(crate) use multithread::*;
72
73        } else {
74            mod singlethread;
75            pub(crate) use singlethread::*;
76         }
77    }
78}
79
80/// Runs a Rust `Future` on the current thread.
81///
82/// The `future` must be `'static` because it will be scheduled
83/// to run in the background and cannot contain any stack references.
84///
85/// The `future` will always be run on the next microtask tick even if it
86/// immediately returns `Poll::Ready`.
87///
88/// # Panics
89///
90/// This function has the same panic behavior as `future_to_promise`.
91#[inline]
92pub fn spawn_local<F>(future: F)
93where
94    F: Future<Output = ()> + 'static,
95{
96    task::Task::spawn(future);
97}
98
99struct Inner {
100    result: Option<Result<JsValue, JsValue>>,
101    task: Option<Waker>,
102    callbacks: Option<(Closure<dyn FnMut(JsValue)>, Closure<dyn FnMut(JsValue)>)>,
103}
104
105/// A Rust `Future` backed by a JavaScript `Promise`.
106///
107/// This type is constructed with a JavaScript `Promise` object and translates
108/// it to a Rust `Future`. This type implements the `Future` trait from the
109/// `futures` crate and will either succeed or fail depending on what happens
110/// with the JavaScript `Promise`.
111///
112/// Currently this type is constructed with `JsFuture::from`.
113pub struct JsFuture {
114    inner: Rc<RefCell<Inner>>,
115}
116
117impl core::panic::UnwindSafe for JsFuture {}
118
119impl fmt::Debug for JsFuture {
120    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
121        write!(f, "JsFuture {{ ... }}")
122    }
123}
124
125impl From<Promise> for JsFuture {
126    fn from(js: Promise) -> JsFuture {
127        // Use the `then` method to schedule two callbacks, one for the
128        // resolved value and one for the rejected value. We're currently
129        // assuming that JS engines will unconditionally invoke precisely one of
130        // these callbacks, no matter what.
131        //
132        // Ideally we'd have a way to cancel the callbacks getting invoked and
133        // free up state ourselves when this `JsFuture` is dropped. We don't
134        // have that, though, and one of the callbacks is likely always going to
135        // be invoked.
136        //
137        // As a result we need to make sure that no matter when the callbacks
138        // are invoked they are valid to be called at any time, which means they
139        // have to be self-contained. Through the `Closure::once` and some
140        // `Rc`-trickery we can arrange for both instances of `Closure`, and the
141        // `Rc`, to all be destroyed once the first one is called.
142        let state = Rc::new(RefCell::new(Inner {
143            result: None,
144            task: None,
145            callbacks: None,
146        }));
147
148        fn finish(state: &RefCell<Inner>, val: Result<JsValue, JsValue>) {
149            let task = {
150                let mut state = state.borrow_mut();
151                debug_assert!(state.callbacks.is_some());
152                debug_assert!(state.result.is_none());
153
154                // First up drop our closures as they'll never be invoked again and
155                // this is our chance to clean up their state.
156                drop(state.callbacks.take());
157
158                // Next, store the value into the internal state.
159                state.result = Some(val);
160                state.task.take()
161            };
162
163            // And then finally if any task was waiting on the value wake it up and
164            // let them know it's there.
165            if let Some(task) = task {
166                task.wake()
167            }
168        }
169
170        let resolve = {
171            let state = state.clone();
172            Closure::once(move |val| finish(&state, Ok(val)))
173        };
174
175        let reject = {
176            let state = state.clone();
177            Closure::once(move |val| finish(&state, Err(val)))
178        };
179
180        let _ = js.then2(&resolve, &reject);
181
182        state.borrow_mut().callbacks = Some((resolve, reject));
183
184        JsFuture { inner: state }
185    }
186}
187
188impl Future for JsFuture {
189    type Output = Result<JsValue, JsValue>;
190
191    fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
192        let mut inner = self.inner.borrow_mut();
193
194        // If our value has come in then we return it...
195        if let Some(val) = inner.result.take() {
196            return Poll::Ready(val);
197        }
198
199        // ... otherwise we arrange ourselves to get woken up once the value
200        // does come in
201        inner.task = Some(cx.waker().clone());
202        Poll::Pending
203    }
204}
205
206/// Converts a Rust `Future` into a JavaScript `Promise`.
207///
208/// This function will take any future in Rust and schedule it to be executed,
209/// returning a JavaScript `Promise` which can then be passed to JavaScript.
210///
211/// The `future` must be `'static` because it will be scheduled to run in the
212/// background and cannot contain any stack references.
213///
214/// The returned `Promise` will be resolved or rejected when the future
215/// completes, depending on whether it finishes with `Ok` or `Err`.
216///
217/// # Panics
218///
219/// Note that in Wasm panics are currently translated to aborts, but "abort" in
220/// this case means that a JavaScript exception is thrown. The Wasm module is
221/// still usable (likely erroneously) after Rust panics.
222#[cfg(not(all(target_arch = "wasm32", feature = "std", panic = "unwind")))]
223pub fn future_to_promise<F>(future: F) -> Promise
224where
225    F: Future<Output = Result<JsValue, JsValue>> + 'static,
226{
227    let mut future = Some(future);
228
229    Promise::new(&mut |resolve, reject| {
230        let future = future.take().unwrap_throw();
231
232        spawn_local(async move {
233            match future.await {
234                Ok(val) => {
235                    resolve.call1(&JsValue::undefined(), &val).unwrap_throw();
236                }
237                Err(val) => {
238                    reject.call1(&JsValue::undefined(), &val).unwrap_throw();
239                }
240            }
241        });
242    })
243}
244
245/// Converts a Rust `Future` into a JavaScript `Promise`.
246///
247/// This function will take any future in Rust and schedule it to be executed,
248/// returning a JavaScript `Promise` which can then be passed to JavaScript.
249///
250/// The `future` must be `'static` because it will be scheduled to run in the
251/// background and cannot contain any stack references.
252///
253/// The returned `Promise` will be resolved or rejected when the future
254/// completes, depending on whether it finishes with `Ok` or `Err`.
255///
256/// # Panics
257///
258/// If the `future` provided panics then the returned `Promise` will be rejected
259/// with a PanicError.
260#[cfg(all(target_arch = "wasm32", feature = "std", panic = "unwind"))]
261pub fn future_to_promise<F>(future: F) -> Promise
262where
263    F: Future<Output = Result<JsValue, JsValue>> + 'static + std::panic::UnwindSafe,
264{
265    let mut future = Some(future);
266    Promise::new(&mut |resolve, reject| {
267        let future = future.take().unwrap_throw();
268        spawn_local(async move {
269            let res = future.catch_unwind().await;
270            match res {
271                Ok(Ok(val)) => {
272                    resolve.call1(&JsValue::undefined(), &val).unwrap_throw();
273                }
274                Ok(Err(val)) => {
275                    reject.call1(&JsValue::undefined(), &val).unwrap_throw();
276                }
277                Err(val) => {
278                    reject
279                        .call1(&JsValue::undefined(), &panic_to_panic_error(val))
280                        .unwrap_throw();
281                }
282            }
283        });
284    })
285}