use std::{future::Future, pin::Pin, task};
use super::{JsArray, JsFunction};
use crate::{
builtins::{
promise::{PromiseState, ResolvingFunctions},
Promise,
},
context::intrinsics::StandardConstructors,
job::NativeJob,
object::{FunctionObjectBuilder, JsObject, JsObjectType, ObjectData},
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<'_>) -> JsResult<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(),
ObjectData::promise(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)?;
}
Ok(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(),
ObjectData::promise(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 resolve<V: Into<JsValue>>(value: V, context: &mut Context<'_>) -> JsResult<Self> {
Promise::promise_resolve(
&context.intrinsics().constructors().promise().constructor(),
value.into(),
context,
)
.and_then(Self::from_object)
}
pub fn reject<E: Into<JsError>>(error: E, context: &mut Context<'_>) -> JsResult<Self> {
Promise::promise_reject(
&context.intrinsics().constructors().promise().constructor(),
&error.into(),
context,
)
.and_then(Self::from_object)
}
#[inline]
pub fn state(&self) -> JsResult<PromiseState> {
let promise = self.inner.borrow();
let promise = promise
.as_promise()
.ok_or_else(|| JsNativeError::typ().with_message("object is not a Promise"))?;
Ok(promise.state().clone())
}
#[inline]
pub fn then(
&self,
on_fulfilled: Option<JsFunction>,
on_rejected: Option<JsFunction>,
context: &mut Context<'_>,
) -> JsResult<Self> {
let result_promise = Promise::inner_then(self, on_fulfilled, on_rejected, context)?;
Self::from_object(result_promise)
}
#[inline]
pub fn catch(&self, on_rejected: JsFunction, context: &mut Context<'_>) -> JsResult<Self> {
self.then(None, Some(on_rejected), context)
}
#[inline]
pub fn finally(&self, on_finally: JsFunction, context: &mut Context<'_>) -> JsResult<Self> {
let c = self.species_constructor(StandardConstructors::promise, context)?;
let (then, catch) = Promise::then_catch_finally_closures(c, on_finally, context);
self.then(Some(then), Some(catch), context)
}
pub fn all<I>(promises: I, context: &mut Context<'_>) -> JsResult<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)?;
let value = value
.as_object()
.expect("Promise.all always returns an object on success");
Self::from_object(value.clone())
}
pub fn all_settled<I>(promises: I, context: &mut Context<'_>) -> JsResult<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)?;
let value = value
.as_object()
.expect("Promise.allSettled always returns an object on success");
Self::from_object(value.clone())
}
pub fn any<I>(promises: I, context: &mut Context<'_>) -> JsResult<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)?;
let value = value
.as_object()
.expect("Promise.any always returns an object on success");
Self::from_object(value.clone())
}
pub fn race<I>(promises: I, context: &mut Context<'_>) -> JsResult<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)?;
let value = value
.as_object()
.expect("Promise.race always returns an object on success");
Self::from_object(value.clone())
}
pub fn into_js_future(self, context: &mut Context<'_>) -> JsResult<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();
FunctionObjectBuilder::new(
context,
NativeFunction::from_copy_closure_with_captures(
move |_, args, state, _| {
finish(state, Ok(args.get_or_undefined(0).clone()));
Ok(JsValue::undefined())
},
state,
),
)
.build()
};
let reject = {
let state = state.clone();
FunctionObjectBuilder::new(
context,
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,
),
)
.build()
};
drop(self.then(Some(resolve), Some(reject), context)?);
Ok(JsFuture { inner: state })
}
}
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 JsObjectType for JsPromise {}
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
}
}