oden/oden-js/src/promise.rs

124 lines
3.2 KiB
Rust

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<dyn FnOnce(&ContextRef) -> 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<T>(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<T>(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<T> 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}");
}
}
}