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