extern crate alloc;
use crate::Promise;
use alloc::rc::Rc;
use core::cell::RefCell;
use core::fmt;
use core::future::{Future, IntoFuture};
use core::panic::AssertUnwindSafe;
use core::pin::Pin;
use core::task::{Context, Poll, Waker};
#[cfg(all(target_arch = "wasm32", feature = "std", panic = "unwind"))]
use futures_util::FutureExt;
use wasm_bindgen::__rt::marker::ErasableGeneric;
#[cfg(all(target_arch = "wasm32", feature = "std", panic = "unwind"))]
use wasm_bindgen::__rt::panic_to_panic_error;
use wasm_bindgen::convert::{FromWasmAbi, Upcast};
use wasm_bindgen::{prelude::*, JsError, JsGeneric};
#[cfg_attr(docsrs, doc(cfg(feature = "futures-core-03-stream")))]
#[cfg(feature = "futures-core-03-stream")]
pub mod stream;
mod queue;
mod task {
use cfg_if::cfg_if;
cfg_if! {
if #[cfg(target_feature = "atomics")] {
mod wait_async_polyfill;
mod multithread;
pub(crate) use multithread::*;
} else {
mod singlethread;
pub(crate) use singlethread::*;
}
}
}
#[inline]
pub fn spawn_local<F>(future: F)
where
F: Future<Output = ()> + 'static,
{
task::Task::spawn(future);
}
struct Inner<T = JsValue> {
result: Option<Result<T, JsValue>>,
task: Option<Waker>,
callbacks: Option<(
Closure<dyn FnMut(T) -> Result<(), JsError>>,
Closure<dyn FnMut(JsValue) -> Result<(), JsError>>,
)>,
}
pub struct JsFuture<T = JsValue> {
inner: Rc<RefCell<Inner<T>>>,
}
impl core::panic::UnwindSafe for JsFuture {}
unsafe impl<T> ErasableGeneric for JsFuture<T> {
type Repr = JsFuture<JsValue>;
}
impl<T, Target> Upcast<JsFuture<Target>> for JsFuture<T> where T: Upcast<Target> {}
impl<T> fmt::Debug for JsFuture<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "JsFuture {{ ... }}")
}
}
impl<T: 'static + FromWasmAbi> From<Promise<T>> for JsFuture<T> {
fn from(js: Promise<T>) -> JsFuture<T> {
let state = Rc::new(RefCell::new(Inner::<T> {
result: None,
task: None,
callbacks: None,
}));
fn finish<T>(state: &RefCell<Inner<T>>, val: Result<T, JsValue>) {
let task = {
let mut state = state.borrow_mut();
assert!(
state.callbacks.is_some(),
"finish: callbacks should be Some"
);
assert!(state.result.is_none(), "finish: result should be None");
drop(state.callbacks.take());
state.result = Some(val);
state.task.take()
};
if let Some(task) = task {
task.wake()
}
}
let resolve = {
let state = AssertUnwindSafe(state.clone());
Closure::once(move |val: T| {
finish(&*state, Ok(val));
Ok(())
})
};
let reject = {
let state = AssertUnwindSafe(state.clone());
Closure::once(move |val| {
finish(&*state, Err(val));
Ok(())
})
};
let _ = js.then_with_reject(&resolve, &reject);
state.borrow_mut().callbacks = Some((resolve, reject));
JsFuture { inner: state }
}
}
impl<T> Future for JsFuture<T> {
type Output = Result<T, JsValue>;
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
let mut inner = self.inner.borrow_mut();
if let Some(val) = inner.result.take() {
return Poll::Ready(val);
}
inner.task = Some(cx.waker().clone());
Poll::Pending
}
}
impl<T: 'static + FromWasmAbi> IntoFuture for Promise<T> {
type Output = Result<T, JsValue>;
type IntoFuture = JsFuture<T>;
fn into_future(self) -> JsFuture<T> {
JsFuture::from(self)
}
}
#[cfg(not(all(target_arch = "wasm32", feature = "std", panic = "unwind")))]
pub fn future_to_promise<F>(future: F) -> Promise
where
F: Future<Output = Result<JsValue, JsValue>> + 'static,
{
let mut future = Some(future);
Promise::new_typed(&mut move |resolve, reject| {
let future = future.take().unwrap_throw();
spawn_local(async move {
match future.await {
Ok(val) => {
resolve.call(&JsValue::undefined(), (&val,)).unwrap_throw();
}
Err(val) => {
reject.call(&JsValue::undefined(), (&val,)).unwrap_throw();
}
}
});
})
}
#[cfg(all(target_arch = "wasm32", feature = "std", panic = "unwind"))]
pub fn future_to_promise<F>(future: F) -> Promise
where
F: Future<Output = Result<JsValue, JsValue>> + 'static + std::panic::UnwindSafe,
{
let mut future = core::panic::AssertUnwindSafe(Some(future));
Promise::new(&mut move |resolve, reject| {
let future = future.take().unwrap_throw();
spawn_local(async move {
let res = future.catch_unwind().await;
match res {
Ok(Ok(val)) => {
resolve.call(&JsValue::undefined(), (&val,)).unwrap_throw();
}
Ok(Err(val)) => {
reject.call(&JsValue::undefined(), (&val,)).unwrap_throw();
}
Err(val) => {
reject
.call(&JsValue::undefined(), (&panic_to_panic_error(val),))
.unwrap_throw();
}
}
});
})
}
pub fn future_to_promise_typed<F, T>(future: F) -> Promise<T>
where
F: Future<Output = Result<T, JsValue>> + 'static,
T: FromWasmAbi + JsGeneric + Upcast<T> + 'static,
{
let mut future = Some(future);
Promise::new_typed(&mut move |resolve, reject| {
let future = future.take().unwrap_throw();
spawn_local(async move {
match future.await {
Ok(val) => {
resolve.call(&JsValue::undefined(), (&val,)).unwrap_throw();
}
Err(val) => {
reject.call(&JsValue::undefined(), (&val,)).unwrap_throw();
}
}
});
})
}