use std::{future::Future, pin::Pin, task};
use super::{JsArray, JsFunction};
use crate::{
builtins::{
promise::{PromiseState, ResolvingFunctions},
Promise,
},
job::NativeJob,
object::JsObject,
value::TryFromJs,
Context, JsArgs, JsError, JsNativeError, JsResult, JsValue, NativeFunction,
};
use boa_gc::{Finalize, Gc, GcRefCell, Trace};
#[derive(Debug, Clone, Trace, Finalize)]
pub struct JsPromise {
inner: JsObject,
}
impl JsPromise {
pub fn new<F>(executor: F, context: &mut Context) -> Self
where
F: FnOnce(&ResolvingFunctions, &mut Context) -> JsResult<JsValue>,
{
let promise = JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
context.intrinsics().constructors().promise().prototype(),
Promise::new(),
);
let resolvers = Promise::create_resolving_functions(&promise, context);
if let Err(e) = executor(&resolvers, context) {
let e = e.to_opaque(context);
resolvers
.reject
.call(&JsValue::undefined(), &[e], context)
.expect("default `reject` function cannot throw");
}
Self { inner: promise }
}
#[inline]
pub fn new_pending(context: &mut Context) -> (Self, ResolvingFunctions) {
let promise = JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
context.intrinsics().constructors().promise().prototype(),
Promise::new(),
);
let resolvers = Promise::create_resolving_functions(&promise, context);
let promise =
Self::from_object(promise).expect("this shouldn't fail with a newly created promise");
(promise, resolvers)
}
#[inline]
pub fn from_object(object: JsObject) -> JsResult<Self> {
if !object.is::<Promise>() {
return Err(JsNativeError::typ()
.with_message("`object` is not a Promise")
.into());
}
Ok(Self { inner: object })
}
pub fn from_future<Fut>(future: Fut, context: &mut Context) -> Self
where
Fut: std::future::IntoFuture<Output = JsResult<JsValue>> + 'static,
{
let (promise, resolvers) = Self::new_pending(context);
let future = async move {
let result = future.await;
NativeJob::new(move |context| match result {
Ok(v) => resolvers.resolve.call(&JsValue::undefined(), &[v], context),
Err(e) => {
let e = e.to_opaque(context);
resolvers.reject.call(&JsValue::undefined(), &[e], context)
}
})
};
context
.job_queue()
.enqueue_future_job(Box::pin(future), context);
promise
}
pub fn from_result<V: Into<JsValue>, E: Into<JsError>>(
value: Result<V, E>,
context: &mut Context,
) -> Self {
match value {
Ok(v) => Self::resolve(v, context),
Err(e) => Self::reject(e, context),
}
}
pub fn resolve<V: Into<JsValue>>(value: V, context: &mut Context) -> Self {
Promise::promise_resolve(
&context.intrinsics().constructors().promise().constructor(),
value.into(),
context,
)
.and_then(Self::from_object)
.expect("default resolving functions cannot throw and must return a promise")
}
pub fn reject<E: Into<JsError>>(error: E, context: &mut Context) -> Self {
Promise::promise_reject(
&context.intrinsics().constructors().promise().constructor(),
&error.into(),
context,
)
.and_then(Self::from_object)
.expect("default resolving functions cannot throw and must return a promise")
}
#[inline]
#[must_use]
pub fn state(&self) -> PromiseState {
self.inner
.downcast_ref::<Promise>()
.expect("objects cannot change type after creation")
.state()
.clone()
}
#[inline]
#[allow(clippy::return_self_not_must_use)] pub fn then(
&self,
on_fulfilled: Option<JsFunction>,
on_rejected: Option<JsFunction>,
context: &mut Context,
) -> Self {
Promise::inner_then(self, on_fulfilled, on_rejected, context)
.and_then(Self::from_object)
.expect("`inner_then` cannot fail for native `JsPromise`")
}
#[inline]
#[allow(clippy::return_self_not_must_use)] pub fn catch(&self, on_rejected: JsFunction, context: &mut Context) -> Self {
self.then(None, Some(on_rejected), context)
}
#[inline]
#[allow(clippy::return_self_not_must_use)] pub fn finally(&self, on_finally: JsFunction, context: &mut Context) -> Self {
let (then, catch) = Promise::then_catch_finally_closures(
context.intrinsics().constructors().promise().constructor(),
on_finally,
context,
);
Promise::inner_then(self, Some(then), Some(catch), context)
.and_then(Self::from_object)
.expect("`inner_then` cannot fail for native `JsPromise`")
}
pub fn all<I>(promises: I, context: &mut Context) -> Self
where
I: IntoIterator<Item = Self>,
{
let promises = JsArray::from_iter(promises.into_iter().map(JsValue::from), context);
let c = &context
.intrinsics()
.constructors()
.promise()
.constructor()
.into();
let value = Promise::all(c, &[promises.into()], context)
.expect("Promise.all cannot fail with the default `%Promise%` constructor");
let object = value
.as_object()
.expect("`Promise.all` always returns an object on success");
Self::from_object(object.clone())
.expect("`Promise::all` with the default `%Promise%` constructor always returns a native `JsPromise`")
}
pub fn all_settled<I>(promises: I, context: &mut Context) -> Self
where
I: IntoIterator<Item = Self>,
{
let promises = JsArray::from_iter(promises.into_iter().map(JsValue::from), context);
let c = &context
.intrinsics()
.constructors()
.promise()
.constructor()
.into();
let value = Promise::all_settled(c, &[promises.into()], context)
.expect("`Promise.all_settled` cannot fail with the default `%Promise%` constructor");
let object = value
.as_object()
.expect("`Promise.all_settled` always returns an object on success");
Self::from_object(object.clone())
.expect("`Promise::all_settled` with the default `%Promise%` constructor always returns a native `JsPromise`")
}
pub fn any<I>(promises: I, context: &mut Context) -> Self
where
I: IntoIterator<Item = Self>,
{
let promises = JsArray::from_iter(promises.into_iter().map(JsValue::from), context);
let c = &context
.intrinsics()
.constructors()
.promise()
.constructor()
.into();
let value = Promise::any(c, &[promises.into()], context)
.expect("`Promise.any` cannot fail with the default `%Promise%` constructor");
let object = value
.as_object()
.expect("`Promise.any` always returns an object on success");
Self::from_object(object.clone())
.expect("`Promise::any` with the default `%Promise%` constructor always returns a native `JsPromise`")
}
pub fn race<I>(promises: I, context: &mut Context) -> Self
where
I: IntoIterator<Item = Self>,
{
let promises = JsArray::from_iter(promises.into_iter().map(JsValue::from), context);
let c = &context
.intrinsics()
.constructors()
.promise()
.constructor()
.into();
let value = Promise::race(c, &[promises.into()], context)
.expect("`Promise.race` cannot fail with the default `%Promise%` constructor");
let object = value
.as_object()
.expect("`Promise.race` always returns an object on success");
Self::from_object(object.clone())
.expect("`Promise::race` with the default `%Promise%` constructor always returns a native `JsPromise`")
}
pub fn into_js_future(self, context: &mut Context) -> JsFuture {
fn finish(state: &GcRefCell<Inner>, val: JsResult<JsValue>) {
let task = {
let mut state = state.borrow_mut();
debug_assert!(state.result.is_none());
state.result = Some(val);
state.task.take()
};
if let Some(task) = task {
task.wake();
}
}
let state = Gc::new(GcRefCell::new(Inner {
result: None,
task: None,
}));
let resolve = {
let state = state.clone();
NativeFunction::from_copy_closure_with_captures(
move |_, args, state, _| {
finish(state, Ok(args.get_or_undefined(0).clone()));
Ok(JsValue::undefined())
},
state,
)
};
let reject = {
let state = state.clone();
NativeFunction::from_copy_closure_with_captures(
move |_, args, state, _| {
let err = JsError::from_opaque(args.get_or_undefined(0).clone());
finish(state, Err(err));
Ok(JsValue::undefined())
},
state,
)
};
drop(self.then(
Some(resolve.to_js_function(context.realm())),
Some(reject.to_js_function(context.realm())),
context,
));
JsFuture { inner: state }
}
pub fn await_blocking(&self, context: &mut Context) -> Result<JsValue, JsValue> {
loop {
match self.state() {
PromiseState::Pending => {
context.run_jobs();
}
PromiseState::Fulfilled(f) => break Ok(f),
PromiseState::Rejected(r) => break Err(r),
}
}
}
}
impl From<JsPromise> for JsObject {
#[inline]
fn from(o: JsPromise) -> Self {
o.inner.clone()
}
}
impl From<JsPromise> for JsValue {
#[inline]
fn from(o: JsPromise) -> Self {
o.inner.clone().into()
}
}
impl std::ops::Deref for JsPromise {
type Target = JsObject;
#[inline]
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl TryFromJs for JsPromise {
fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult<Self> {
match value {
JsValue::Object(o) => Self::from_object(o.clone()),
_ => Err(JsNativeError::typ()
.with_message("value is not a Promise object")
.into()),
}
}
}
pub struct JsFuture {
inner: Gc<GcRefCell<Inner>>,
}
impl std::fmt::Debug for JsFuture {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("JsFuture").finish_non_exhaustive()
}
}
#[derive(Trace, Finalize)]
struct Inner {
result: Option<JsResult<JsValue>>,
#[unsafe_ignore_trace]
task: Option<task::Waker>,
}
impl Future for JsFuture {
type Output = JsResult<JsValue>;
fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll<Self::Output> {
let mut inner = self.inner.borrow_mut();
if let Some(result) = inner.result.take() {
return task::Poll::Ready(result);
}
inner.task = Some(cx.waker().clone());
task::Poll::Pending
}
}