Skip to main content

js_sys/futures/
mod.rs

1//! Converting between JavaScript `Promise`s to Rust `Future`s.
2//!
3//! This module 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 module 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
33extern crate alloc;
34
35use crate::Promise;
36use alloc::rc::Rc;
37use core::cell::RefCell;
38use core::fmt;
39use core::future::{Future, IntoFuture};
40use core::panic::AssertUnwindSafe;
41use core::pin::Pin;
42use core::task::{Context, Poll, Waker};
43#[cfg(all(
44    all(target_family = "wasm", not(target_os = "wasi")),
45    feature = "std",
46    panic = "unwind"
47))]
48use futures_util::FutureExt;
49use wasm_bindgen::__rt::marker::ErasableGeneric;
50#[cfg(all(
51    all(target_family = "wasm", not(target_os = "wasi")),
52    feature = "std",
53    panic = "unwind"
54))]
55use wasm_bindgen::__rt::panic_to_panic_error;
56use wasm_bindgen::convert::{FromWasmAbi, Upcast};
57use wasm_bindgen::sys::Promising;
58use wasm_bindgen::{prelude::*, JsError, JsGeneric};
59
60#[cfg_attr(docsrs, doc(cfg(feature = "futures-core-03-stream")))]
61#[cfg(feature = "futures-core-03-stream")]
62pub mod stream;
63
64mod queue;
65
66mod task {
67    use cfg_if::cfg_if;
68
69    cfg_if! {
70        if #[cfg(target_feature = "atomics")] {
71            mod wait_async_polyfill;
72            mod multithread;
73            pub(crate) use multithread::*;
74
75        } else {
76            mod singlethread;
77            pub(crate) use singlethread::*;
78         }
79    }
80}
81
82/// Runs a Rust `Future` on the current thread.
83///
84/// The `future` must be `'static` because it will be scheduled
85/// to run in the background and cannot contain any stack references.
86///
87/// The `future` will always be run on the next microtask tick even if it
88/// immediately returns `Poll::Ready`.
89///
90/// # Panics
91///
92/// This function has the same panic behavior as `future_to_promise`.
93#[inline]
94pub fn spawn_local<F>(future: F)
95where
96    F: Future<Output = ()> + 'static,
97{
98    task::Task::spawn(future);
99}
100
101struct Inner<T = JsValue> {
102    result: Option<Result<T, JsValue>>,
103    task: Option<Waker>,
104    callbacks: Option<(
105        Closure<dyn FnMut(T) -> Result<(), JsError>>,
106        Closure<dyn FnMut(JsValue) -> Result<(), JsError>>,
107    )>,
108}
109
110/// A Rust `Future` backed by a JavaScript `Promise`.
111///
112/// This type is constructed with a JavaScript `Promise` object and translates
113/// it to a Rust `Future`. This type implements the `Future` trait from the
114/// `futures` crate and will either succeed or fail depending on what happens
115/// with the JavaScript `Promise`.
116///
117/// Currently this type is constructed with `JsFuture::from`.
118pub struct JsFuture<T = JsValue> {
119    inner: Rc<RefCell<Inner<T>>>,
120}
121
122impl core::panic::UnwindSafe for JsFuture {}
123
124unsafe impl<T> ErasableGeneric for JsFuture<T> {
125    type Repr = JsFuture<JsValue>;
126}
127
128// Upcast for JsFuture is covariant in T (the success type)
129// JsFuture<T> can upcast to JsFuture<Target> if T: Upcast<Target>
130impl<T, Target> Upcast<JsFuture<Target>> for JsFuture<T> where T: Upcast<Target> {}
131
132impl<T> fmt::Debug for JsFuture<T> {
133    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
134        write!(f, "JsFuture {{ ... }}")
135    }
136}
137
138// `FromWasmAbi` is what the closure shim invokes on the resolved value;
139// no layout equivalence with `JsValue` is required at this seam — the
140// per-type `from_abi` does the conversion (e.g. for dynamic unions it
141// runs the variant dispatcher).
142impl<T: FromWasmAbi + 'static> From<Promise<T>> for JsFuture<T> {
143    fn from(js: Promise<T>) -> JsFuture<T> {
144        // Use the `then` method to schedule two callbacks, one for the
145        // resolved value and one for the rejected value. We're currently
146        // assuming that JS engines will unconditionally invoke precisely one of
147        // these callbacks, no matter what.
148        //
149        // Ideally we'd have a way to cancel the callbacks getting invoked and
150        // free up state ourselves when this `JsFuture` is dropped. We don't
151        // have that, though, and one of the callbacks is likely always going to
152        // be invoked.
153        //
154        // As a result we need to make sure that no matter when the callbacks
155        // are invoked they are valid to be called at any time, which means they
156        // have to be self-contained. Through the `Closure::once` and some
157        // `Rc`-trickery we can arrange for both instances of `Closure`, and the
158        // `Rc`, to all be destroyed once the first one is called.
159        let state = Rc::new(RefCell::new(Inner::<T> {
160            result: None,
161            task: None,
162            callbacks: None,
163        }));
164
165        fn finish<T>(state: &RefCell<Inner<T>>, val: Result<T, JsValue>) {
166            let task = {
167                let mut state = state.borrow_mut();
168                assert!(
169                    state.callbacks.is_some(),
170                    "finish: callbacks should be Some"
171                );
172                assert!(state.result.is_none(), "finish: result should be None");
173
174                // First up drop our closures as they'll never be invoked again and
175                // this is our chance to clean up their state.
176                drop(state.callbacks.take());
177
178                // Next, store the value into the internal state.
179                state.result = Some(val);
180                state.task.take()
181            };
182
183            // And then finally if any task was waiting on the value wake it up and
184            // let them know it's there.
185            if let Some(task) = task {
186                task.wake()
187            }
188        }
189
190        let resolve = {
191            let state = AssertUnwindSafe(state.clone());
192            Closure::once(move |val: T| {
193                finish(&*state, Ok(val));
194                Ok(())
195            })
196        };
197
198        let reject = {
199            let state = AssertUnwindSafe(state.clone());
200            Closure::once(move |val| {
201                finish(&*state, Err(val));
202                Ok(())
203            })
204        };
205
206        let _ = js.then_with_reject(&resolve, &reject);
207
208        state.borrow_mut().callbacks = Some((resolve, reject));
209
210        JsFuture { inner: state }
211    }
212}
213
214impl<T> Future for JsFuture<T> {
215    type Output = Result<T, JsValue>;
216
217    fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
218        let mut inner = self.inner.borrow_mut();
219
220        // If our value has come in then we return it...
221        if let Some(val) = inner.result.take() {
222            return Poll::Ready(val);
223        }
224
225        // ... otherwise we arrange ourselves to get woken up once the value
226        // does come in
227        inner.task = Some(cx.waker().clone());
228        Poll::Pending
229    }
230}
231
232impl<T: FromWasmAbi + 'static> IntoFuture for Promise<T> {
233    type Output = Result<T, JsValue>;
234    type IntoFuture = JsFuture<T>;
235
236    fn into_future(self) -> JsFuture<T> {
237        JsFuture::from(self)
238    }
239}
240
241/// Converts a Rust `Future` into a JavaScript `Promise`.
242///
243/// This function will take any future in Rust and schedule it to be executed,
244/// returning a JavaScript `Promise` which can then be passed to JavaScript.
245///
246/// The `future` must be `'static` because it will be scheduled to run in the
247/// background and cannot contain any stack references.
248///
249/// The returned `Promise` will be resolved or rejected when the future
250/// completes, depending on whether it finishes with `Ok` or `Err`.
251///
252/// # Panics
253///
254/// Note that in Wasm panics are currently translated to aborts, but "abort" in
255/// this case means that a JavaScript exception is thrown. The Wasm module is
256/// still usable (likely erroneously) after Rust panics.
257#[cfg(not(all(
258    all(target_family = "wasm", not(target_os = "wasi")),
259    feature = "std",
260    panic = "unwind"
261)))]
262pub fn future_to_promise<F>(future: F) -> Promise
263where
264    F: Future<Output = Result<JsValue, JsValue>> + 'static,
265{
266    let mut future = Some(future);
267
268    Promise::new_typed(&mut move |resolve, reject| {
269        let future = future.take().unwrap_throw();
270
271        spawn_local(async move {
272            match future.await {
273                Ok(val) => {
274                    resolve.call(&JsValue::UNDEFINED, (&val,)).unwrap_throw();
275                }
276                Err(val) => {
277                    reject.call(&JsValue::UNDEFINED, (&val,)).unwrap_throw();
278                }
279            }
280        });
281    })
282}
283
284/// Converts a Rust `Future` into a JavaScript `Promise`.
285///
286/// This function will take any future in Rust and schedule it to be executed,
287/// returning a JavaScript `Promise` which can then be passed to JavaScript.
288///
289/// The `future` must be `'static` because it will be scheduled to run in the
290/// background and cannot contain any stack references.
291///
292/// The returned `Promise` will be resolved or rejected when the future
293/// completes, depending on whether it finishes with `Ok` or `Err`.
294///
295/// # Panics
296///
297/// If the `future` provided panics then the returned `Promise` will be rejected
298/// with a PanicError.
299#[cfg(all(
300    all(target_family = "wasm", not(target_os = "wasi")),
301    feature = "std",
302    panic = "unwind"
303))]
304pub fn future_to_promise<F>(future: F) -> Promise
305where
306    F: Future<Output = Result<JsValue, JsValue>> + 'static + std::panic::UnwindSafe,
307{
308    // Wrap `future` in AssertUnwindSafe and move it into the closure so the closure
309    // satisfies MaybeUnwindSafe (required when panic=unwind). Using `move` avoids
310    // capturing a `&mut` reference, which is never UnwindSafe. The Promise executor
311    // is not called inside a panic-catching context, so this is always safe.
312    let mut future = core::panic::AssertUnwindSafe(Some(future));
313    Promise::new(&mut move |resolve, reject| {
314        let future = future.take().unwrap_throw();
315        spawn_local(async move {
316            let res = future.catch_unwind().await;
317            match res {
318                Ok(Ok(val)) => {
319                    resolve.call(&JsValue::UNDEFINED, (&val,)).unwrap_throw();
320                }
321                Ok(Err(val)) => {
322                    reject.call(&JsValue::UNDEFINED, (&val,)).unwrap_throw();
323                }
324                Err(val) => {
325                    reject
326                        .call(&JsValue::UNDEFINED, (&panic_to_panic_error(val),))
327                        .unwrap_throw();
328                }
329            }
330        });
331    })
332}
333
334// Note: Once we bump MSRV, we can type future_to_promise with backwards compatible inference.
335/// Converts a Rust `Future` into a corresponding typed JavaScript `Promise<T>`.
336///
337/// This function will take any future in Rust and schedule it to be executed,
338/// returning a JavaScript `Promise` which can then be passed to JavaScript.
339///
340/// The `future` must be `'static` because it will be scheduled to run in the
341/// background and cannot contain any stack references.
342///
343/// The returned `Promise` will be resolved or rejected when the future completes,
344/// depending on whether it finishes with `Ok` or `Err`.
345///
346/// # Panics
347///
348/// Note that in Wasm panics are currently translated to aborts, but "abort" in
349/// this case means that a JavaScript exception is thrown. The Wasm module is
350/// still usable (likely erroneously) after Rust panics.
351///
352/// If the `future` provided panics then the returned `Promise` **will not
353/// resolve**. Instead it will be a leaked promise. This is an unfortunate
354/// limitation of Wasm currently that's hoped to be fixed one day!
355pub fn future_to_promise_typed<T, F>(future: F) -> Promise<<T as Promising>::Resolution>
356where
357    F: Future<Output = Result<T, JsValue>> + 'static,
358    T: Promising + FromWasmAbi + JsGeneric,
359    <T as Promising>::Resolution: JsGeneric,
360{
361    let mut future = Some(future);
362
363    Promise::new_typed(&mut move |resolve, reject| {
364        let future = future.take().unwrap_throw();
365        spawn_local(async move {
366            match future.await {
367                Ok(val) => {
368                    resolve.call(&JsValue::UNDEFINED, (&val,)).unwrap_throw();
369                }
370                Err(val) => {
371                    reject.call(&JsValue::UNDEFINED, (&val,)).unwrap_throw();
372                }
373            }
374        });
375    })
376}