use crate::rong::spawn_local;
use crate::{
FromJSValue, IntoJSValue, JSContext, JSContextImpl, JSErrorFactory, JSFunc, JSObject,
JSObjectOps, JSResult, JSTypeOf, JSValue, JSValueImpl, PromiseHandlerRegistration, RongJSError,
function::JSParameterType,
};
use std::cell::RefCell;
use std::future::Future;
use std::marker::PhantomData;
use std::ops::Deref;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll, Waker};
type PromiseResult<V> = Result<(Promise<V>, JSFunc<V>, JSFunc<V>), RongJSError>;
pub struct Promise<V: JSValueImpl> {
obj: JSObject<V>,
}
impl<V: JSValueImpl> Clone for Promise<V> {
fn clone(&self) -> Self {
Self {
obj: self.obj.clone(),
}
}
}
impl<V: JSValueImpl> Deref for Promise<V> {
type Target = JSObject<V>;
fn deref(&self) -> &Self::Target {
&self.obj
}
}
impl<V> IntoJSValue<V> for Promise<V>
where
V: JSValueImpl,
{
fn into_js_value(self, _ctx: &JSContext<V::Context>) -> JSValue<V> {
self.obj.into_js_value()
}
}
impl<V> FromJSValue<V> for Promise<V>
where
V: JSTypeOf,
{
fn from_js_value(ctx: &JSContext<V::Context>, value: JSValue<V>) -> JSResult<Self> {
let obj = JSObject::from_js_value(ctx, value)?;
Ok(Self { obj })
}
}
impl<C: JSContextImpl> JSContext<C> {
pub fn promise(&self) -> PromiseResult<C::Value>
where
C::Value: JSTypeOf,
{
let (promise, resolver, reject) = self.as_ref().promise();
let promise = JSObject::from_js_value(self, JSValue::from_raw(self, promise))?;
let resolver = JSFunc::from_js_value(self, JSValue::from_raw(self, resolver))?;
let reject = JSFunc::from_js_value(self, JSValue::from_raw(self, reject))?;
Ok((Promise { obj: promise }, resolver, reject))
}
}
impl<V: JSValueImpl + 'static> Promise<V>
where
V: JSObjectOps,
{
pub fn new(ctx: &JSContext<V::Context>) -> PromiseResult<V> {
ctx.promise()
}
pub fn from_future<F, R>(
ctx: &JSContext<V::Context>,
root: Option<V>,
future: F,
) -> JSResult<Promise<V>>
where
F: Future<Output = R> + 'static,
R: IntoJSValue<V> + 'static,
R: PromiseResolver<V>,
{
let (promise, resolve, reject) = ctx.promise()?;
spawn_local(async move {
let result = future.await;
let _keep_root_alive = root;
result.resolve_promise(resolve, reject);
});
Ok(promise)
}
pub fn then(&self) -> JSResult<JSFunc<V>> {
self.obj.get("then")
}
pub fn catch(&self) -> JSResult<JSFunc<V>> {
self.obj.get("catch")
}
pub fn into_object(self) -> JSObject<V> {
self.obj
}
}
pub trait PromiseResolver<V: JSValueImpl> {
fn resolve_promise(self, resolve: JSFunc<V>, reject: JSFunc<V>);
}
fn drain_microtasks<V>(ctx: &JSContext<V::Context>)
where
V: JSValueImpl,
<V::Context as crate::JSContextImpl>::Runtime: 'static,
{
let _ = ctx.runtime().run_pending_jobs();
}
impl<V> PromiseResolver<V> for RongJSError
where
V: JSObjectOps + crate::JSArrayOps,
V::Context: JSErrorFactory,
<V::Context as crate::JSContextImpl>::Runtime: 'static,
{
fn resolve_promise(self, _resolve: JSFunc<V>, reject: JSFunc<V>) {
let ctx = reject.context();
let js_err = self.into_catch_value(&ctx);
let _ = reject.call::<_, ()>(None, (js_err,));
drain_microtasks::<V>(&ctx);
}
}
impl<V, T> PromiseResolver<V> for T
where
T: IntoJSValue<V> + JSParameterType,
V: JSObjectOps,
<V::Context as crate::JSContextImpl>::Runtime: 'static,
{
fn resolve_promise(self, resolve: JSFunc<V>, _reject: JSFunc<V>) {
let ctx = resolve.context();
let _ = resolve.call::<_, ()>(None, (self,));
drain_microtasks::<V>(&ctx);
}
}
impl<V, T> PromiseResolver<V> for Vec<T>
where
V: JSObjectOps + JSTypeOf + crate::JSArrayOps + 'static,
T: IntoJSValue<V>,
<V::Context as crate::JSContextImpl>::Runtime: 'static,
{
fn resolve_promise(self, resolve: JSFunc<V>, _reject: JSFunc<V>) {
let ctx = resolve.context();
let arg = <Vec<T> as IntoJSValue<V>>::into_js_value(self, &ctx).into_value();
let this = V::create_undefined(ctx.as_ref());
let argv = [arg];
let _ = ctx.as_ref().call(resolve.as_value(), this, &argv);
drain_microtasks::<V>(&ctx);
}
}
impl<V, T> PromiseResolver<V> for JSResult<T>
where
T: IntoJSValue<V>,
V: JSObjectOps + crate::JSArrayOps,
V::Context: JSErrorFactory,
<V::Context as crate::JSContextImpl>::Runtime: 'static,
{
fn resolve_promise(self, resolve: JSFunc<V>, reject: JSFunc<V>) {
match self {
Ok(value) => {
let ctx = resolve.context();
let arg = <T as IntoJSValue<V>>::into_js_value(value, &ctx).into_value();
let this = V::create_undefined(ctx.as_ref());
let argv = [arg];
let _ = ctx.as_ref().call(resolve.as_value(), this, &argv);
drain_microtasks::<V>(&ctx);
}
Err(err) => {
let ctx = reject.context();
let js_error_value = err.into_catch_value(&ctx).into_value();
let this = V::create_undefined(ctx.as_ref());
let argv = [js_error_value];
let _ = ctx.as_ref().call(reject.as_value(), this, &argv);
drain_microtasks::<V>(&ctx);
}
}
}
}
pub struct PromiseFuture<V: JSValueImpl, T> {
state: Option<Rc<RefCell<PromiseState<T>>>>,
promise: Promise<V>,
_marker: PhantomData<T>,
}
enum PromiseState<T> {
Pending(Waker),
Resolved(JSResult<T>),
}
impl<V: JSValueImpl, T> Unpin for PromiseFuture<V, T> {}
fn promise_state_is_pending<T>(state: &Rc<RefCell<PromiseState<T>>>) -> bool {
matches!(&*state.borrow(), PromiseState::Pending(_))
}
fn attach_js_promise_handlers<V>(
promise: &Promise<V>,
resolve: &JSFunc<V>,
reject: &JSFunc<V>,
) -> JSResult<()>
where
V: JSValueImpl + JSObjectOps + 'static,
{
promise
.then()?
.call::<_, ()>(Some(promise.obj.clone()), (resolve.clone(), reject.clone()))
}
fn register_future_handlers<V, T>(
ctx: &JSContext<V::Context>,
promise: &Promise<V>,
resolve: &JSFunc<V>,
reject: &JSFunc<V>,
state: &Rc<RefCell<PromiseState<T>>>,
) -> JSResult<()>
where
V: JSValueImpl + JSObjectOps + 'static,
<V::Context as crate::JSContextImpl>::Runtime: 'static,
{
match ctx.as_ref().register_promise_handlers(
promise.obj.as_value(),
resolve.as_value(),
reject.as_value(),
) {
PromiseHandlerRegistration::JavaScriptOnly => {
attach_js_promise_handlers(promise, resolve, reject)?;
drain_microtasks::<V>(ctx);
}
PromiseHandlerRegistration::NativeOnly => {
drain_microtasks::<V>(ctx);
}
PromiseHandlerRegistration::NativeWithJavaScriptFallbackIfPending => {
drain_microtasks::<V>(ctx);
if promise_state_is_pending(state) {
attach_js_promise_handlers(promise, resolve, reject)?;
drain_microtasks::<V>(ctx);
}
}
}
Ok(())
}
impl<V: JSValueImpl + 'static> Promise<V> {
pub fn into_future<T>(self) -> PromiseFuture<V, T>
where
T: FromJSValue<V> + 'static,
{
PromiseFuture {
state: None,
promise: self,
_marker: PhantomData::<T>,
}
}
}
impl<V: JSValueImpl + 'static, T> Future for PromiseFuture<V, T>
where
T: FromJSValue<V> + 'static,
V: JSObjectOps,
{
type Output = JSResult<T>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
if this.state.is_none() {
let state = Rc::new(RefCell::new(PromiseState::Pending(cx.waker().clone())));
this.state = Some(state.clone());
let ctx = &this.promise.obj.context();
let state = this.state.clone().unwrap();
let resolve_state = state.clone();
let resolve = JSFunc::new(ctx, move |ctx: JSContext<V::Context>, value: JSValue<V>| {
let mut state = resolve_state.borrow_mut();
if !matches!(&*state, PromiseState::Pending(_)) {
return;
}
let resolved = T::from_js_value(&ctx, value);
if let PromiseState::Pending(waker) =
std::mem::replace(&mut *state, PromiseState::Resolved(resolved))
{
waker.wake_by_ref();
}
})
.unwrap();
let reject_state = state.clone();
let reject = JSFunc::new(
ctx,
move |_ctx: JSContext<V::Context>, reason: JSValue<V>| {
let mut state = reject_state.borrow_mut();
if !matches!(&*state, PromiseState::Pending(_)) {
return;
}
if let PromiseState::Pending(waker) = std::mem::replace(
&mut *state,
PromiseState::Resolved(Err(RongJSError::from_thrown_value(reason))),
) {
waker.wake_by_ref();
}
},
)
.unwrap();
register_future_handlers(ctx, &this.promise, &resolve, &reject, &state)?;
if let Some(state) = &this.state {
let s = state.borrow();
if matches!(&*s, PromiseState::Resolved(_)) {
drop(s);
let mut s = state.borrow_mut();
match std::mem::replace(&mut *s, PromiseState::Pending(cx.waker().clone())) {
PromiseState::Resolved(result) => return Poll::Ready(result),
other => {
*s = other;
}
}
}
}
return Poll::Pending;
}
if let Some(state) = &this.state {
let mut state = state.borrow_mut();
match &*state {
PromiseState::Resolved(Ok(_)) => {
if let PromiseState::Resolved(Ok(success)) =
std::mem::replace(&mut *state, PromiseState::Pending(cx.waker().clone()))
{
return Poll::Ready(Ok(success));
}
}
PromiseState::Resolved(Err(_)) => {
if let PromiseState::Resolved(Err(err)) =
std::mem::replace(&mut *state, PromiseState::Pending(cx.waker().clone()))
{
return Poll::Ready(Err(err));
}
}
PromiseState::Pending(_) => {
*state = PromiseState::Pending(cx.waker().clone());
}
}
}
Poll::Pending
}
}