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