use crate::{ContextRef, ValueRef, ValueResult}; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::mpsc::Sender; #[derive(Eq, PartialEq, Hash, Debug, Clone, Copy)] pub(crate) struct PromiseHandle(u64); impl PromiseHandle { pub fn new() -> Self { static NEXT_ID: AtomicU64 = AtomicU64::new(0); PromiseHandle(NEXT_ID.fetch_add(1, Ordering::SeqCst)) } } pub type PromiseResult = Box ValueResult + Send + 'static>; pub(crate) enum PromiseEvent { Resolved(PromiseResult), Rejected(PromiseResult), } /// A Promise is a small, thread-safe marker which represents a pending /// promise inside the JS runtime. This is how you complete the promise: you /// must either call `resolve` or `reject` before you drop the promise. If /// you drop the promise without calling either `resolve` or `reject` then it /// will panic. /// /// In order to actually *process* the completion, someone must call /// process_all_jobs on a context or the underlying runtime itself. #[derive(Debug)] pub struct Promise { complete: bool, handle: PromiseHandle, channel: Sender<(PromiseHandle, PromiseEvent)>, } impl Promise { pub(crate) fn new( handle: PromiseHandle, channel: Sender<(PromiseHandle, PromiseEvent)>, ) -> Self { Promise { complete: false, handle, channel, } } pub fn resolve(mut self, value: T) where T: FnOnce(&ContextRef) -> ValueResult + Send + 'static, { let _ = self .channel .send((self.handle, PromiseEvent::Resolved(Box::new(value)))); self.complete = true; } pub fn reject(mut self, value: T) where T: FnOnce(&ContextRef) -> ValueResult + Send + 'static, { let _ = self .channel .send((self.handle, PromiseEvent::Rejected(Box::new(value)))); self.complete = true; } } impl Drop for Promise { fn drop(&mut self) { assert!(self.complete); } } pub trait RejectedPromiseTracker { fn on_rejected_promise( &self, ctx: &ContextRef, promise: &ValueRef, reason: &ValueRef, is_handled: bool, ); } impl RejectedPromiseTracker for T where T: Fn(&ContextRef, &ValueRef, &ValueRef, bool) -> (), { fn on_rejected_promise( &self, ctx: &ContextRef, promise: &ValueRef, reason: &ValueRef, is_handled: bool, ) { self(ctx, promise, reason, is_handled); } } pub struct DefaultRejectedPromiseTracker {} impl DefaultRejectedPromiseTracker { pub fn new() -> Self { DefaultRejectedPromiseTracker {} } } impl RejectedPromiseTracker for DefaultRejectedPromiseTracker { fn on_rejected_promise( &self, ctx: &ContextRef, _promise: &ValueRef, reason: &ValueRef, is_handled: bool, ) { if !is_handled { let reason_str = reason.to_string(ctx).expect( "Unhandled rejected promise: reason unknown: unable to convert reason to string", ); panic!("Unhandled rejected promise: {reason_str}"); } } }