use std::{cell::Cell, rc::Rc};
use crate::utils::primordials::Primordial;
use rquickjs::{
atom::PredefinedAtom,
class::{Trace, Tracer},
function::Constructor,
prelude::{IntoArg, OnceFn, This},
promise::PromiseState,
Ctx, Error, FromJs, Function, IntoJs, JsLifetime, Object, Promise, Result, Value,
};
pub fn promise_rejected_with<'js>(
primordials: &PromisePrimordials<'js>,
value: Value<'js>,
) -> Result<Promise<'js>> {
primordials
.promise_reject
.call((This(primordials.promise_constructor.clone()), value))
}
pub fn promise_rejected_catch<'js>(
ctx: &Ctx<'js>,
promise_primordials: &PromisePrimordials<'js>,
) -> Result<Promise<'js>> {
promise_rejected_with(promise_primordials, ctx.catch())
}
pub fn promise_rejected_with_constructor<'js, T: From<Error>>(
constructor: &Constructor<'js>,
promise_primordials: &PromisePrimordials<'js>,
msg: &str,
) -> std::result::Result<Promise<'js>, T> {
let e: Value = constructor.call((msg,))?;
Ok(promise_rejected_with(promise_primordials, e)?)
}
pub fn promise_resolved_with<'js>(
ctx: &Ctx<'js>,
primordials: &PromisePrimordials<'js>,
value: Result<Value<'js>>,
) -> Result<Promise<'js>> {
match value {
Ok(value) => primordials
.promise_resolve
.call((This(primordials.promise_constructor.clone()), value)),
Err(Error::Exception) => primordials
.promise_reject
.call((This(primordials.promise_constructor.clone()), ctx.catch())),
Err(err) => Err(err),
}
}
#[derive(JsLifetime, Clone)]
pub struct PromisePrimordials<'js> {
pub promise_constructor: Constructor<'js>,
pub promise_resolve: Function<'js>,
pub promise_reject: Function<'js>,
pub promise_all: Function<'js>,
pub promise_resolved_with_undefined: Promise<'js>,
pub promise_prototype_then: Function<'js>,
}
impl<'js> Trace<'js> for PromisePrimordials<'js> {
fn trace<'a>(&self, tracer: Tracer<'a, 'js>) {
self.promise_constructor.trace(tracer);
self.promise_resolve.trace(tracer);
self.promise_reject.trace(tracer);
self.promise_all.trace(tracer);
self.promise_resolved_with_undefined.trace(tracer);
self.promise_prototype_then.trace(tracer);
}
}
impl<'js> Primordial<'js> for PromisePrimordials<'js> {
fn new(ctx: &Ctx<'js>) -> Result<Self>
where
Self: Sized,
{
let promise_constructor: Constructor<'js> = ctx.globals().get(PredefinedAtom::Promise)?;
let promise_resolve: Function<'js> = promise_constructor.get("resolve")?;
let promise_reject: Function<'js> = promise_constructor.get("reject")?;
let promise_all: Function<'js> = promise_constructor.get("all")?;
let promise_prototype_then: Function<'js> = promise_constructor
.get::<_, Object>("prototype")?
.get("then")?;
let promise_resolved_with_undefined = promise_resolve.call((
This(promise_constructor.clone()),
Value::new_undefined(ctx.clone()),
))?;
Ok(Self {
promise_constructor,
promise_resolve,
promise_reject,
promise_all,
promise_resolved_with_undefined,
promise_prototype_then,
})
}
}
pub fn upon_promise<'js, Input: FromJs<'js> + 'js, Output: IntoJs<'js> + 'js>(
ctx: Ctx<'js>,
promise: Promise<'js>,
then: impl FnOnce(Ctx<'js>, std::result::Result<Input, Value<'js>>) -> Result<Output> + 'js,
) -> Result<Promise<'js>> {
let promise_then = PromisePrimordials::get(&ctx)?
.promise_prototype_then
.clone();
let then_cb = Rc::new(Cell::new(Some(then)));
let then_cb2 = then_cb.clone();
promise_then.call((
This(promise),
Function::new(
ctx.clone(),
OnceFn::new(move |ctx, input| {
then_cb
.take()
.expect("Promise.then should only call either resolve or reject")(
ctx,
Ok(input),
)
}),
),
Function::new(
ctx,
OnceFn::new(move |ctx, e: Value<'js>| {
then_cb2
.take()
.expect("Promise.then should only call either resolve or reject")(
ctx, Err(e)
)
}),
),
))
}
pub fn upon_promise_fulfilment<'js, Input: FromJs<'js> + 'js, Output: IntoJs<'js> + 'js>(
ctx: Ctx<'js>,
promise: Promise<'js>,
then: impl FnOnce(Ctx<'js>, Input) -> Result<Output> + 'js,
) -> Result<Promise<'js>> {
let promise_then = PromisePrimordials::get(&ctx)?
.promise_prototype_then
.clone();
promise_then.call((This(promise), Function::new(ctx.clone(), OnceFn::new(then))))
}
#[derive(Debug, JsLifetime, Clone)]
pub struct ResolveablePromise<'js> {
pub promise: Promise<'js>,
resolve: Option<Function<'js>>,
reject: Option<Function<'js>>,
}
impl<'js> ResolveablePromise<'js> {
pub fn new(ctx: &Ctx<'js>) -> Result<Self> {
let (promise, resolve, reject) = Promise::new(ctx)?;
Ok(Self {
promise,
resolve: Some(resolve),
reject: Some(reject),
})
}
pub fn resolved_with_undefined(primordials: &PromisePrimordials<'js>) -> Self {
Self {
promise: primordials.promise_resolved_with_undefined.clone(),
resolve: None,
reject: None,
}
}
pub fn rejected_with(primordials: &PromisePrimordials<'js>, error: Value<'js>) -> Result<Self> {
Ok(Self {
promise: promise_rejected_with(primordials, error)?,
resolve: None,
reject: None,
})
}
pub fn rejected_with_constructor(
primordials: &PromisePrimordials<'js>,
constructor: &Constructor<'js>,
msg: &str,
) -> Result<Self> {
Ok(Self {
promise: promise_rejected_with_constructor::<rquickjs::Error>(
constructor,
primordials,
msg,
)?,
resolve: None,
reject: None,
})
}
pub fn resolve(&self, value: impl IntoArg<'js>) -> Result<()> {
if let Some(resolve) = &self.resolve {
let () = resolve.call((value,))?;
}
Ok(())
}
pub fn resolve_undefined(&self) -> Result<()> {
if let Some(resolve) = &self.resolve {
let () = resolve.call((rquickjs::Undefined,))?;
}
Ok(())
}
pub fn reject(&self, value: impl IntoArg<'js>) -> Result<()> {
if let Some(reject) = &self.reject {
let () = reject.call((value,))?;
}
Ok(())
}
pub fn reject_with_constructor(&self, constructor: &Constructor<'js>, msg: &str) -> Result<()> {
if let Some(reject) = &self.reject {
let e: Value = constructor.call((msg,))?;
let () = reject.call((e,))?;
}
Ok(())
}
pub fn is_pending(&self) -> bool {
self.promise.state() == PromiseState::Pending
}
pub fn set_is_handled(&self) -> Result<()> {
self.promise.catch()?.call((
This(self.promise.clone()),
Function::new(self.promise.ctx().clone(), || {}),
))
}
}
impl<'js> Trace<'js> for ResolveablePromise<'js> {
fn trace<'a>(&self, tracer: Tracer<'a, 'js>) {
self.promise.trace(tracer);
self.resolve.trace(tracer);
self.reject.trace(tracer);
}
}
pub fn with_promise_result<'js>(
ctx: &Ctx<'js>,
f: impl FnOnce() -> Result<Promise<'js>>,
) -> Result<Promise<'js>> {
match f() {
Ok(value) => Ok(value),
Err(Error::Exception) => promise_rejected_catch(ctx, &*PromisePrimordials::get(ctx)?),
Err(err) => Err(err),
}
}