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