js_function_promisify/
callback_pair.rs

1use core::cell::RefCell;
2use js_sys::Function;
3use std::fmt::Debug;
4use std::future::Future;
5use std::rc::Rc;
6use std::task::Poll;
7use std::task::Waker;
8use wasm_bindgen::prelude::Closure;
9use wasm_bindgen::JsValue;
10
11/// A `CallbackPair<F>` is a wrapper around a `wasm_bindgen::prelude::Closure<F>` which supports TODO:
12#[derive(Debug)]
13pub struct CallbackPair<A, B>
14where
15  A: 'static + ?Sized,
16  B: 'static + ?Sized,
17{
18  inner: Rc<RefCell<CallbackPairInner<A, B>>>,
19}
20
21impl<A, B> CallbackPair<A, B>
22where
23  A: 'static + ?Sized,
24  B: 'static + ?Sized,
25{
26  pub fn new<X, Y>(x: X, y: Y) -> CallbackPair<A, B>
27  where
28    Self: From<(X, Y)>,
29  {
30    Self::from((x, y))
31  }
32
33  pub fn as_functions(&self) -> (Function, Function) {
34    let left: JsValue = self
35      .inner
36      .borrow()
37      .cb
38      .as_ref()
39      .unwrap()
40      .as_ref()
41      .0
42      .as_ref()
43      .into();
44    let right: JsValue = self
45      .inner
46      .borrow()
47      .cb
48      .as_ref()
49      .unwrap()
50      .as_ref()
51      .1
52      .as_ref()
53      .into();
54    (left.into(), right.into())
55  }
56
57  pub fn as_closures(&self) -> Rc<(Closure<A>, Closure<B>)> {
58    Rc::clone(self.inner.borrow().cb.as_ref().unwrap())
59  }
60}
61
62/// The Default impl for CallbackPair creates a pair of single-arg `(resolve, reject)` callbacks,
63/// similar to the javascript Promise contsructor.
64impl Default for CallbackPair<dyn FnMut(JsValue), dyn FnMut(JsValue)> {
65  fn default() -> Self {
66    Self::from((|data| Ok(data), |err| Err(err)))
67  }
68}
69
70/// Standard impl of Future for CallbackPair.
71impl<A, B> Future for CallbackPair<A, B>
72where
73  A: 'static + ?Sized,
74  B: 'static + ?Sized,
75{
76  type Output = Result<JsValue, JsValue>;
77
78  fn poll(
79    self: std::pin::Pin<&mut Self>,
80    cx: &mut std::task::Context<'_>,
81  ) -> std::task::Poll<Self::Output> {
82    let mut inner = self.inner.borrow_mut();
83    if let Some(val) = inner.result.take() {
84      return Poll::Ready(val);
85    }
86    inner.task = Some(cx.waker().clone());
87    Poll::Pending
88  }
89}
90
91/// A utility macro for generating every possible implementation of `From<A, B> for CallbackPair`.
92macro_rules! from_impl {
93  // The main arm of this macro. Generates a single From impl for CallbackPair.
94  // a - The list of parameter types that FnMut A takes.
95  // b - The list of parameter types that FnMut B takes.
96  // alist - The argument list of A.
97  // blist - The argument list of B.
98  (($($a:ty),*), ($($b:ty),*), ($($alist:ident),*), ($($blist:ident),*)) => {
99    impl<A, B> From<(A, B)> for CallbackPair<dyn FnMut($($a,)*), dyn FnMut($($b,)*)>
100    where
101      A: 'static + FnOnce($($a,)*) -> Result<JsValue, JsValue>,
102      B: 'static + FnOnce($($b,)*) -> Result<JsValue, JsValue>,
103    {
104      fn from(cb: (A, B)) -> Self {
105        let inner = CallbackPairInner::new();
106        let state = Rc::clone(&inner);
107        let cb0 = cb.0;
108        let left = Closure::once(move |$($alist),*| CallbackPairInner::finish(&state, cb0($($alist),*)));
109        let state = Rc::clone(&inner);
110        let cb1 = cb.1;
111        let right = Closure::once(move |$($blist),*| CallbackPairInner::finish(&state, cb1($($blist),*)));
112        let ptr = Rc::new((left, right));
113        inner.borrow_mut().cb = Some(ptr);
114        CallbackPair { inner }
115      }
116    }
117  };
118  // Shorthand for the main arm. Based on the argument list, generate the parameter types (always JsValue) for that list.
119  (($($a:ident,)*), ($($b:ident,)*)) => {
120    from_impl!(($(from_impl!(@rep $a JsValue)),*), ($(from_impl!(@rep $b JsValue)),*), ($($a),*), ($($b),*));
121  };
122  // Recursively generates a set of impls where the left arg list stays the same and the right arg list gets smaller.
123  (@left ($($a:ident,)*); $head:ident $($tail:tt)*) => {
124    from_impl!(($($a,)*), ($head, $($tail,)*));
125    from_impl!(@left ($($a,)*); $($tail)*);
126  };
127  // Recursively generates a set of impls where the right arg list stays the same and the left arg list gets smaller.
128  (@right ($($b:ident,)*); $head:ident $($tail:tt)*) => {
129    from_impl!(($head, $($tail,)*), ($($b,)*));
130    from_impl!(@right ($($b,)*); $($tail)*);
131  };
132  // For a list of identifiers, creates every set of possible combinations of those identifiers and generates From<A, B> impls for them.
133  ($head:ident $($tail:tt)*) => {
134    // Generate a From impl for the full set of arguments on both sides.
135    from_impl!(($head, $($tail,)*), ($head, $($tail,)*));
136    // Using the same set of arguments on the left side, recursively generate a From impl for every possible set of args on the right.
137    from_impl!(@left ($head, $($tail,)*); $($tail)*);
138    // An empty arg list will never be generated, so impl it here.
139    from_impl!(($head, $($tail,)*), ());
140    // Using the same set of arguments on the right side, recursively generate a From impl for every possible set of args on the left.
141    from_impl!(@right ($head, $($tail,)*); $($tail)*);
142    // An empty arg list will never be generated, so impl it here.
143    from_impl!((), ($head, $($tail,)*));
144    // Recurse inwards, generating the same definitions with one less argument.
145    from_impl!($($tail)*);
146  };
147  // Utility for replacing anything with a type.
148  (@rep $_t:tt $sub:ty) => {
149    $sub
150  };
151  // Empty arms for handling the end of recursion.
152  () => {
153    from_impl!((), ());
154  };
155  (@left ($($a:ident,)*); ) => {};
156  (@right ($($b:ident,)*); ) => {};
157}
158
159from_impl!(a0 a1 a2 a3 a4 a5 a6); // Generate From impls for every possible permutation of arguments in either callback, up to 7.
160
161#[derive(Debug)]
162pub struct CallbackPairInner<A, B>
163where
164  A: 'static + ?Sized,
165  B: 'static + ?Sized,
166{
167  cb: Option<Rc<(Closure<A>, Closure<B>)>>,
168  result: Option<Result<JsValue, JsValue>>,
169  task: Option<Waker>,
170}
171
172impl<A, B> CallbackPairInner<A, B>
173where
174  A: 'static + ?Sized,
175  B: 'static + ?Sized,
176{
177  pub fn new() -> Rc<RefCell<CallbackPairInner<A, B>>> {
178    Rc::new(RefCell::new(CallbackPairInner {
179      cb: None,
180      task: None,
181      result: None,
182    }))
183  }
184
185  pub fn finish(state: &RefCell<CallbackPairInner<A, B>>, val: Result<JsValue, JsValue>) {
186    let task = {
187      let mut state = state.borrow_mut();
188      debug_assert!(state.result.is_none());
189      debug_assert!(state.cb.is_some());
190      drop(state.cb.take());
191      state.result = Some(val);
192      state.task.take()
193    };
194    if let Some(task) = task {
195      task.wake()
196    }
197  }
198}
199
200#[cfg(test)]
201mod tests {
202  use crate::CallbackPair;
203  use std::rc::Rc;
204  use wasm_bindgen::JsCast;
205  use wasm_bindgen_test::*;
206  use web_sys::{window, IdbOpenDbRequest};
207
208  wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
209
210  /// We could write a macro for this, but then I wouldn't be totally confident it captured every permutation.
211  /// For now, we relish in the beauty of our christmas tree.
212  #[wasm_bindgen_test]
213  #[rustfmt::skip]
214  fn should_compile_with_any_args() {
215    let _r = CallbackPair::new(|| Ok("".into()), || Err("".into()));
216    let _r = CallbackPair::new(|_a| Ok("".into()), || Err("".into()));
217    let _r = CallbackPair::new(|_a, _b| Ok("".into()), || Err("".into()));
218    let _r = CallbackPair::new(|_a, _b, _c| Ok("".into()), || Err("".into()));
219    let _r = CallbackPair::new(|_a, _b, _c, _d| Ok("".into()), || Err("".into()));
220    let _r = CallbackPair::new(|_a, _b, _c, _d, _e| Ok("".into()), || Err("".into()));
221    let _r = CallbackPair::new(|_a, _b, _c, _d, _e, _f| Ok("".into()), || Err("".into()));
222    let _r = CallbackPair::new(|_a, _b, _c, _d, _e, _f, _g| Ok("".into()), || Err("".into()));
223    let _r = CallbackPair::new(|| Ok("".into()), |_a| Err("".into()));
224    let _r = CallbackPair::new(|_a| Ok("".into()), |_a| Err("".into()));
225    let _r = CallbackPair::new(|_a, _b| Ok("".into()), |_a| Err("".into()));
226    let _r = CallbackPair::new(|_a, _b, _c| Ok("".into()), |_a| Err("".into()));
227    let _r = CallbackPair::new(|_a, _b, _c, _d| Ok("".into()), |_a| Err("".into()));
228    let _r = CallbackPair::new(|_a, _b, _c, _d, _e| Ok("".into()), |_a| Err("".into()));
229    let _r = CallbackPair::new(|_a, _b, _c, _d, _e, _f| Ok("".into()), |_a| Err("".into()));
230    let _r = CallbackPair::new(|_a, _b, _c, _d, _e, _f, _g| Ok("".into()), |_a| Err("".into()));
231    let _r = CallbackPair::new(|| Ok("".into()), |_a, _b| Err("".into()));
232    let _r = CallbackPair::new(|_a| Ok("".into()), |_a, _b| Err("".into()));
233    let _r = CallbackPair::new(|_a, _b| Ok("".into()), |_a, _b| Err("".into()));
234    let _r = CallbackPair::new(|_a, _b, _c| Ok("".into()), |_a, _b| Err("".into()));
235    let _r = CallbackPair::new(|_a, _b, _c, _d| Ok("".into()), |_a, _b| Err("".into()));
236    let _r = CallbackPair::new(|_a, _b, _c, _d, _e| Ok("".into()), |_a, _b| Err("".into()));
237    let _r = CallbackPair::new(|_a, _b, _c, _d, _e, _f| Ok("".into()), |_a, _b| Err("".into()));
238    let _r = CallbackPair::new(|_a, _b, _c, _d, _e, _f, _g| Ok("".into()), |_a, _b| Err("".into()));
239    let _r = CallbackPair::new(|| Ok("".into()), |_a, _b, _c| Err("".into()));
240    let _r = CallbackPair::new(|_a| Ok("".into()), |_a, _b, _c| Err("".into()));
241    let _r = CallbackPair::new(|_a, _b| Ok("".into()), |_a, _b, _c| Err("".into()));
242    let _r = CallbackPair::new(|_a, _b, _c| Ok("".into()), |_a, _b, _c| Err("".into()));
243    let _r = CallbackPair::new(|_a, _b, _c, _d| Ok("".into()), |_a, _b, _c| Err("".into()));
244    let _r = CallbackPair::new(|_a, _b, _c, _d, _e| Ok("".into()), |_a, _b, _c| Err("".into()));
245    let _r = CallbackPair::new(|_a, _b, _c, _d, _e, _f| Ok("".into()), |_a, _b, _c| Err("".into()));
246    let _r = CallbackPair::new(|_a, _b, _c, _d, _e, _f, _g| Ok("".into()), |_a, _b, _c| Err("".into()));
247    let _r = CallbackPair::new(|| Ok("".into()), |_a, _b, _c, _d| Err("".into()));
248    let _r = CallbackPair::new(|_a| Ok("".into()), |_a, _b, _c, _d| Err("".into()));
249    let _r = CallbackPair::new(|_a, _b| Ok("".into()), |_a, _b, _c, _d| Err("".into()));
250    let _r = CallbackPair::new(|_a, _b, _c| Ok("".into()), |_a, _b, _c, _d| Err("".into()));
251    let _r = CallbackPair::new(|_a, _b, _c, _d| Ok("".into()), |_a, _b, _c, _d| Err("".into()));
252    let _r = CallbackPair::new(|_a, _b, _c, _d, _e| Ok("".into()), |_a, _b, _c, _d| Err("".into()));
253    let _r = CallbackPair::new(|_a, _b, _c, _d, _e, _f| Ok("".into()), |_a, _b, _c, _d| Err("".into()));
254    let _r = CallbackPair::new(|_a, _b, _c, _d, _e, _f, _g| Ok("".into()), |_a, _b, _c, _d| Err("".into()));
255    let _r = CallbackPair::new(|| Ok("".into()), |_a, _b, _c, _d, _e| Err("".into()));
256    let _r = CallbackPair::new(|_a| Ok("".into()), |_a, _b, _c, _d, _e| Err("".into()));
257    let _r = CallbackPair::new(|_a, _b| Ok("".into()), |_a, _b, _c, _d, _e| Err("".into()));
258    let _r = CallbackPair::new(|_a, _b, _c| Ok("".into()), |_a, _b, _c, _d, _e| Err("".into()));
259    let _r = CallbackPair::new(|_a, _b, _c, _d| Ok("".into()), |_a, _b, _c, _d, _e| Err("".into()));
260    let _r = CallbackPair::new(|_a, _b, _c, _d, _e| Ok("".into()), |_a, _b, _c, _d, _e| Err("".into()));
261    let _r = CallbackPair::new(|_a, _b, _c, _d, _e, _f| Ok("".into()), |_a, _b, _c, _d, _e| Err("".into()));
262    let _r = CallbackPair::new(|_a, _b, _c, _d, _e, _f, _g| Ok("".into()), |_a, _b, _c, _d, _e| Err("".into()));
263    let _r = CallbackPair::new(|| Ok("".into()), |_a, _b, _c, _d, _e, _f| Err("".into()));
264    let _r = CallbackPair::new(|_a| Ok("".into()), |_a, _b, _c, _d, _e, _f| Err("".into()));
265    let _r = CallbackPair::new(|_a, _b| Ok("".into()), |_a, _b, _c, _d, _e, _f| Err("".into()));
266    let _r = CallbackPair::new(|_a, _b, _c| Ok("".into()), |_a, _b, _c, _d, _e, _f| Err("".into()));
267    let _r = CallbackPair::new(|_a, _b, _c, _d| Ok("".into()), |_a, _b, _c, _d, _e, _f| Err("".into()));
268    let _r = CallbackPair::new(|_a, _b, _c, _d, _e| Ok("".into()), |_a, _b, _c, _d, _e, _f| Err("".into()));
269    let _r = CallbackPair::new(|_a, _b, _c, _d, _e, _f| Ok("".into()), |_a, _b, _c, _d, _e, _f| Err("".into()));
270    let _r = CallbackPair::new(|_a, _b, _c, _d, _e, _f, _g| Ok("".into()), |_a, _b, _c, _d, _e, _f| Err("".into()));
271    let _r = CallbackPair::new(|| Ok("".into()), |_a, _b, _c, _d, _e, _f, _g| Err("".into()));
272    let _r = CallbackPair::new(|_a| Ok("".into()), |_a, _b, _c, _d, _e, _f, _g| Err("".into()));
273    let _r = CallbackPair::new(|_a, _b| Ok("".into()), |_a, _b, _c, _d, _e, _f, _g| Err("".into()));
274    let _r = CallbackPair::new(|_a, _b, _c| Ok("".into()), |_a, _b, _c, _d, _e, _f, _g| Err("".into()));
275    let _r = CallbackPair::new(|_a, _b, _c, _d| Ok("".into()), |_a, _b, _c, _d, _e, _f, _g| Err("".into()));
276    let _r = CallbackPair::new(|_a, _b, _c, _d, _e| Ok("".into()), |_a, _b, _c, _d, _e, _f, _g| Err("".into()));
277    let _r = CallbackPair::new(|_a, _b, _c, _d, _e, _f| Ok("".into()), |_a, _b, _c, _d, _e, _f, _g| Err("".into()));
278    let _r = CallbackPair::new(|_a, _b, _c, _d, _e, _f, _g| Ok("".into()), |_a, _b, _c, _d, _e, _f, _g| Err("".into()));
279  }
280
281  #[wasm_bindgen_test]
282  async fn inner_dropped_after_await() {
283    let future = CallbackPair::new(|| Ok("".into()), || Err("".into()));
284    let req: IdbOpenDbRequest = window()
285      .expect("window not available")
286      .indexed_db()
287      .unwrap()
288      .expect("idb not available")
289      .open("my_db")
290      .expect("Failed to get idb request");
291    let functions = future.as_functions();
292    req.set_onerror(Some(&functions.1));
293    let inner_ref = {
294      let weak_ref = Rc::downgrade(&future.inner);
295      req.set_onsuccess(Some(&functions.0));
296      assert_eq!(weak_ref.upgrade().is_some(), true); // Assert inner_ref `Some`
297      weak_ref
298    };
299    assert_eq!(inner_ref.upgrade().is_some(), true); // Assert inner_ref `Some`
300    future.await.unwrap();
301    assert_eq!(inner_ref.upgrade().is_none(), true); // Assert inner_ref `None`
302  }
303
304  #[wasm_bindgen_test]
305  async fn closure_dropped_after_await() {
306    let future = CallbackPair::new(|| Ok("".into()), || Err("".into()));
307    let req: IdbOpenDbRequest = window()
308      .expect("window not available")
309      .indexed_db()
310      .unwrap()
311      .expect("idb not available")
312      .open("my_db")
313      .expect("Failed to get idb request");
314    let wref = {
315      let closures = future.as_closures();
316      let weak_ref = Rc::downgrade(&closures);
317      req.set_onsuccess(Some(closures.0.as_ref().as_ref().unchecked_ref()));
318      req.set_onerror(Some(closures.1.as_ref().as_ref().unchecked_ref()));
319      assert_eq!(weak_ref.upgrade().is_some(), true); // Assert resolve_ref `Some`
320      weak_ref
321    };
322    assert_eq!(wref.upgrade().is_some(), true); // Assert resolve_ref `Some`
323    future.await.unwrap();
324    assert_eq!(wref.upgrade().is_none(), true); // Assert resolve_ref `None`
325  }
326
327  #[wasm_bindgen_test]
328  async fn new_promise_left_resolve() {
329    let future = CallbackPair::default();
330    web_sys::window()
331      .unwrap()
332      .set_timeout_with_callback_and_timeout_and_arguments_0(future.as_functions().0.as_ref(), 200)
333      .unwrap();
334    let result = future.await;
335    assert_eq!(result.is_ok(), true); // Assert is `Ok`
336  }
337
338  #[wasm_bindgen_test]
339  async fn new_promise_right_reject() {
340    let future = CallbackPair::default();
341    web_sys::window()
342      .unwrap()
343      .set_timeout_with_callback_and_timeout_and_arguments_0(future.as_functions().1.as_ref(), 200)
344      .unwrap();
345    let result = future.await;
346    assert_eq!(result.is_err(), true); // Assert is `Err`
347  }
348}