use crate::core::{
ClosureData, DestructuringElement, Expr, JSObjectDataPtr, Statement, StatementKind, Value, env_set, evaluate_expr, evaluate_statements,
extract_closure_from_value, prepare_function_call_env, value_to_string,
};
use crate::core::{new_js_object_data, obj_get_key_value, obj_set_key_value};
use crate::error::JSError;
fn stmt_expr(expr: Expr) -> Statement {
Statement::from(StatementKind::Expr(expr))
}
fn stmt_return(expr: Option<Expr>) -> Statement {
Statement::from(StatementKind::Return(expr))
}
use crate::js_array::set_array_length;
use crate::unicode::utf8_to_utf16;
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::{Duration, Instant};
#[derive(Clone, Debug)]
enum Task {
Resolution {
promise: Rc<RefCell<JSPromise>>,
callbacks: Vec<(Value, Rc<RefCell<JSPromise>>)>,
},
Rejection {
promise: Rc<RefCell<JSPromise>>,
callbacks: Vec<(Value, Rc<RefCell<JSPromise>>)>,
},
Timeout {
id: usize,
callback: Value,
args: Vec<Value>,
target_time: Instant,
},
Interval {
id: usize,
callback: Value,
args: Vec<Value>,
target_time: Instant,
interval: Duration,
},
UnhandledCheck { promise: Rc<RefCell<JSPromise>>, reason: Value },
}
pub fn take_unhandled_rejection() -> Option<Value> {
UNHANDLED_REJECTION.with(|slot| slot.borrow_mut().take())
}
pub fn peek_unhandled_rejection() -> Option<Value> {
UNHANDLED_REJECTION.with(|slot| slot.borrow().clone())
}
pub fn pending_unhandled_count() -> usize {
PENDING_UNHANDLED_CHECKS.with(|q| q.borrow().len())
}
pub fn task_queue_len() -> usize {
GLOBAL_TASK_QUEUE.with(|q| q.borrow().len())
}
pub fn current_tick() -> usize {
CURRENT_TICK.load(Ordering::SeqCst)
}
thread_local! {
static GLOBAL_TASK_QUEUE: RefCell<Vec<Task>> = const { RefCell::new(Vec::new()) };
static ALLSETTLED_STATES: RefCell<Vec<Rc<RefCell<AllSettledState>>>> = const { RefCell::new(Vec::new()) };
static NEXT_TIMEOUT_ID: RefCell<usize> = const { RefCell::new(1) };
static UNHANDLED_REJECTION: RefCell<Option<Value>> = const { RefCell::new(None) };
#[allow(clippy::type_complexity)]
static PENDING_UNHANDLED_CHECKS: RefCell<Vec<(Rc<RefCell<JSPromise>>, Value, usize)>> = const { RefCell::new(Vec::new()) };
}
static RUN_LOOP_NESTING: AtomicUsize = AtomicUsize::new(0);
static CURRENT_TICK: AtomicUsize = AtomicUsize::new(0);
const UNHANDLED_GRACE: usize = 6;
fn queue_task(task: Task) {
log::debug!("queue_task called with {:?}", task);
let nesting = RUN_LOOP_NESTING.load(Ordering::SeqCst);
log::trace!("queue_task: current RUN_LOOP_NESTING={}", nesting);
log::debug!("queue_task: CURRENT_TICK={} task_queue_len={}", current_tick(), task_queue_len());
GLOBAL_TASK_QUEUE.with(|queue| {
queue.borrow_mut().push(task);
});
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum PollResult {
Executed,
Wait(Duration),
Empty,
}
fn process_task(task: Task) -> Result<(), JSError> {
match task {
Task::Resolution { promise, callbacks } => {
log::trace!("Processing Resolution task with {} callbacks", callbacks.len());
for (callback, new_promise) in callbacks {
if let Some((params, body, captured_env)) = extract_closure_from_value(&callback) {
let args = vec![promise.borrow().value.clone().unwrap_or(Value::Undefined)];
let func_env = prepare_function_call_env(Some(&captured_env), None, Some(¶ms), &args, None, None)?;
match evaluate_statements(&func_env, &body) {
Ok(result) => {
log::trace!("Callback executed successfully, resolving promise");
resolve_promise(&new_promise, result);
}
Err(e) => {
log::trace!("Callback execution failed: {:?}", e);
if let crate::error::JSErrorKind::Throw { value } = e.kind() {
reject_promise(&new_promise, value.clone());
} else {
reject_promise(&new_promise, Value::String(utf8_to_utf16(&format!("{:?}", e))));
}
}
}
} else {
log::trace!("Callback is not a function, resolving with undefined");
resolve_promise(&new_promise, Value::Undefined);
}
}
}
Task::Rejection { promise, callbacks } => {
log::trace!("Processing Rejection task with {} callbacks", callbacks.len());
for (callback, new_promise) in callbacks {
if let Some((params, body, captured_env)) = extract_closure_from_value(&callback) {
let args = vec![promise.borrow().value.clone().unwrap_or(Value::Undefined)];
let func_env = prepare_function_call_env(Some(&captured_env), None, Some(¶ms), &args, None, None)?;
match evaluate_statements(&func_env, &body) {
Ok(result) => {
resolve_promise(&new_promise, result);
}
Err(e) => {
if let crate::error::JSErrorKind::Throw { value } = e.kind() {
reject_promise(&new_promise, value.clone());
} else {
reject_promise(&new_promise, Value::String(utf8_to_utf16(&format!("{:?}", e))));
}
}
}
} else {
resolve_promise(&new_promise, Value::Undefined);
}
}
}
Task::Timeout { id: _, callback, args, .. } => {
log::trace!("Processing Timeout task");
if let Some((params, body, captured_env)) = extract_closure_from_value(&callback) {
let this_val_opt = if let Value::Object(_) = callback {
let mut global_env = captured_env.clone();
while let Some(proto) = global_env.clone().borrow().prototype.clone() {
global_env = proto;
}
Some(Value::Object(global_env))
} else {
None
};
let func_env = prepare_function_call_env(Some(&captured_env), this_val_opt, Some(¶ms), &args, None, None)?;
let _ = evaluate_statements(&func_env, &body)?;
}
}
Task::Interval {
id,
callback,
args,
interval,
..
} => {
log::trace!("Processing Interval task");
if let Some((params, body, captured_env)) = extract_closure_from_value(&callback) {
let this_val_opt = if let Value::Object(_) = callback {
let mut global_env = captured_env.clone();
while let Some(proto) = global_env.clone().borrow().prototype.clone() {
global_env = proto;
}
Some(Value::Object(global_env))
} else {
None
};
let func_env = prepare_function_call_env(Some(&captured_env), this_val_opt, Some(¶ms), &args, None, None)?;
let _ = evaluate_statements(&func_env, &body)?;
queue_task(Task::Interval {
id,
callback: callback.clone(),
args: args.clone(),
target_time: Instant::now() + interval,
interval,
});
}
}
Task::UnhandledCheck { promise, reason } => {
log::trace!("Processing UnhandledCheck task for promise ptr={:p}", Rc::as_ptr(&promise));
let promise_borrow = promise.borrow();
if promise_borrow.on_rejected.is_empty() {
let insertion_tick = CURRENT_TICK.load(Ordering::SeqCst);
log::trace!(
"UnhandledCheck: adding to PENDING_UNHANDLED_CHECKS for promise ptr={:p} insertion_tick={}",
Rc::as_ptr(&promise),
insertion_tick
);
PENDING_UNHANDLED_CHECKS.with(|pending| {
pending.borrow_mut().push((promise.clone(), reason, insertion_tick));
});
} else {
log::trace!("UnhandledCheck: handlers attached, skipping unhandled recording");
}
}
}
Ok(())
}
pub fn poll_event_loop() -> Result<PollResult, JSError> {
let now = Instant::now();
let (task, should_sleep) = GLOBAL_TASK_QUEUE.with(|queue| {
let mut queue_borrow = queue.borrow_mut();
if queue_borrow.is_empty() {
return (None, None);
}
let mut ready_index = None;
let mut min_wait_time: Option<Duration> = None;
for (i, task) in queue_borrow.iter().enumerate() {
match task {
Task::Timeout { target_time, .. } | Task::Interval { target_time, .. } => {
if *target_time <= now {
ready_index = Some(i);
break;
} else {
let wait = *target_time - now;
min_wait_time = Some(min_wait_time.map_or(wait, |m| m.min(wait)));
}
}
_ => {
ready_index = Some(i);
break;
}
}
}
if let Some(index) = ready_index {
(Some(queue_borrow.remove(index)), None)
} else {
(None, min_wait_time)
}
});
if let Some(task) = task {
process_task(task)?;
Ok(PollResult::Executed)
} else if let Some(wait) = should_sleep {
Ok(PollResult::Wait(wait))
} else {
Ok(PollResult::Empty)
}
}
pub fn run_event_loop() -> Result<PollResult, JSError> {
log::trace!("run_event_loop called");
let nesting_before = RUN_LOOP_NESTING.fetch_add(1, Ordering::SeqCst);
log::debug!(
"run_event_loop: incremented RUN_LOOP_NESTING from {} to {}",
nesting_before,
nesting_before + 1
);
let result = poll_event_loop()?;
let processed_any = matches!(result, PollResult::Executed);
if nesting_before == 0 && !processed_any {
let prev_tick = CURRENT_TICK.load(Ordering::SeqCst);
let current = CURRENT_TICK.fetch_add(1, Ordering::SeqCst) + 1;
log::debug!("CURRENT_TICK advanced from {} to {}", prev_tick, current);
PENDING_UNHANDLED_CHECKS.with(|pending| {
let mut pending_borrow = pending.borrow_mut();
if !pending_borrow.is_empty() {
log::trace!(
"Processing PENDING_UNHANDLED_CHECKS: len={} current={}",
pending_borrow.len(),
current
);
let mut new_pending: Vec<(Rc<RefCell<JSPromise>>, Value, usize)> = Vec::new();
for (promise, reason, insertion_tick) in pending_borrow.drain(..) {
let promise_ptr = Rc::as_ptr(&promise);
log::trace!(
"pending entry: promise ptr={:p} insertion_tick={} expires_at={}",
promise_ptr,
insertion_tick,
insertion_tick + UNHANDLED_GRACE
);
let promise_b = promise.borrow();
match &promise_b.state {
PromiseState::Rejected(_val) => {
if !promise_b.on_rejected.is_empty() {
log::trace!("handler attached for promise ptr={:p}, ignoring", promise_ptr);
continue;
}
if current >= insertion_tick + UNHANDLED_GRACE {
log::debug!("pending expired -> recording unhandled for promise ptr={:p}", promise_ptr);
UNHANDLED_REJECTION.with(|slot| {
let mut s = slot.borrow_mut();
if s.is_none() {
*s = Some(reason.clone());
}
});
} else {
log::trace!("pending not yet expired -> requeue promise ptr={:p}", promise_ptr);
new_pending.push((promise.clone(), reason.clone(), insertion_tick));
}
}
_ => {
log::trace!("promise ptr={:p} no longer rejected, ignoring", promise_ptr);
}
}
}
*pending_borrow = new_pending;
}
});
}
RUN_LOOP_NESTING.fetch_sub(1, Ordering::SeqCst);
Ok(result)
}
#[derive(Clone, Debug, Default)]
pub enum PromiseState {
#[default]
Pending,
Fulfilled(Value),
Rejected(Value),
}
#[derive(Clone, Default)]
pub struct JSPromise {
pub state: PromiseState,
pub value: Option<Value>, pub on_fulfilled: Vec<(Value, Rc<RefCell<JSPromise>>)>, pub on_rejected: Vec<(Value, Rc<RefCell<JSPromise>>)>, }
#[derive(Clone, Debug)]
pub enum SettledResult {
Fulfilled(Value),
Rejected(Value),
}
#[derive(Clone, Debug)]
pub struct AllSettledState {
pub results: Vec<Option<SettledResult>>,
pub completed: usize,
pub total: usize,
pub result_promise: Rc<RefCell<JSPromise>>,
pub env: JSObjectDataPtr,
}
impl AllSettledState {
pub fn new(total: usize, result_promise: Rc<RefCell<JSPromise>>, env: JSObjectDataPtr) -> Self {
AllSettledState {
results: vec![None; total],
completed: 0,
total,
result_promise,
env,
}
}
pub fn record_fulfilled(&mut self, index: usize, value: Value) -> Result<(), JSError> {
if index < self.results.len() {
self.results[index] = Some(SettledResult::Fulfilled(value));
self.completed += 1;
self.check_completion()?;
}
Ok(())
}
pub fn record_rejected(&mut self, index: usize, reason: Value) -> Result<(), JSError> {
if index < self.results.len() {
self.results[index] = Some(SettledResult::Rejected(reason));
self.completed += 1;
self.check_completion()?;
}
Ok(())
}
fn check_completion(&self) -> Result<(), JSError> {
log::trace!("check_completion: completed={}, total={}", self.completed, self.total);
if self.completed == self.total {
log::trace!("All promises settled, resolving result promise");
let result_array = crate::js_array::create_array(&self.env)?;
for (i, result) in self.results.iter().enumerate() {
if let Some(settled_result) = result {
let result_obj = Rc::new(RefCell::new(crate::core::JSObjectData::new()));
match settled_result {
SettledResult::Fulfilled(value) => {
obj_set_key_value(&result_obj, &"status".into(), Value::String(utf8_to_utf16("fulfilled")))?;
obj_set_key_value(&result_obj, &"value".into(), value.clone())?;
}
SettledResult::Rejected(reason) => {
obj_set_key_value(&result_obj, &"status".into(), Value::String(utf8_to_utf16("rejected")))?;
obj_set_key_value(&result_obj, &"reason".into(), reason.clone())?;
}
}
obj_set_key_value(&result_array, &i.to_string().into(), Value::Object(result_obj))?;
}
}
set_array_length(&result_array, self.total)?;
log::trace!("Resolving allSettled result promise");
resolve_promise(&self.result_promise, Value::Object(result_array));
}
Ok(())
}
}
impl JSPromise {
pub fn new() -> Self {
JSPromise {
state: PromiseState::Pending,
value: None,
on_fulfilled: Vec::new(),
on_rejected: Vec::new(),
}
}
}
impl std::fmt::Debug for JSPromise {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"JSPromise {{ state: {:?}, on_fulfilled: {}, on_rejected: {} }}",
self.state,
self.on_fulfilled.len(),
self.on_rejected.len()
)
}
}
pub fn make_promise_object() -> Result<JSObjectDataPtr, JSError> {
let promise_obj = Rc::new(RefCell::new(crate::core::JSObjectData::new()));
let then_func = Value::Function("Promise.prototype.then".to_string());
obj_set_key_value(&promise_obj, &"then".into(), then_func)?;
let catch_func = Value::Function("Promise.prototype.catch".to_string());
obj_set_key_value(&promise_obj, &"catch".into(), catch_func)?;
let finally_func = Value::Function("Promise.prototype.finally".to_string());
obj_set_key_value(&promise_obj, &"finally".into(), finally_func)?;
Ok(promise_obj)
}
pub fn handle_promise_constructor(args: &[crate::core::Expr], env: &JSObjectDataPtr) -> Result<Value, JSError> {
handle_promise_constructor_direct(args, env)
}
pub fn handle_promise_constructor_direct(args: &[crate::core::Expr], env: &JSObjectDataPtr) -> Result<Value, JSError> {
if args.is_empty() {
return Err(raise_eval_error!("Promise constructor requires an executor function"));
}
let executor = evaluate_expr(env, &args[0])?;
let (params, captured_env) = if let Some((p, _body, c)) = extract_closure_from_value(&executor) {
(p.clone(), c.clone())
} else {
return Err(raise_eval_error!("Promise constructor requires a function as executor"));
};
let promise = Rc::new(RefCell::new(JSPromise::new()));
let promise_obj = make_promise_object()?;
obj_set_key_value(&promise_obj, &"__promise".into(), Value::Promise(promise.clone()))?;
let resolve_func = create_resolve_function_direct(promise.clone());
let reject_func = create_reject_function_direct(promise.clone());
let executor_args = vec![resolve_func.clone(), reject_func.clone()];
let executor_env = if params.is_empty() {
crate::core::prepare_function_call_env(Some(&captured_env), None, None, &[], None, None)?
} else {
crate::core::prepare_function_call_env(Some(&captured_env), None, Some(¶ms), &executor_args, None, None)?
};
log::trace!("About to call executor function");
let call_expr = Expr::Call(
Box::new(Expr::Value(executor)),
vec![Expr::Value(resolve_func), Expr::Value(reject_func)],
);
match evaluate_expr(&executor_env, &call_expr) {
Ok(_) => {}
Err(e) => {
if let crate::error::JSErrorKind::Throw { value } = e.kind() {
crate::js_promise::reject_promise(&promise, value.clone());
} else {
return Err(e);
}
}
}
log::trace!("Executor function called");
Ok(Value::Object(promise_obj))
}
fn create_resolve_function_direct(promise: Rc<RefCell<JSPromise>>) -> Value {
log::trace!("create_resolve_function_direct called");
let closure_data = ClosureData::new(
&[DestructuringElement::Variable("value".to_string(), None)],
&[stmt_expr(Expr::Call(
Box::new(Expr::Var("__internal_resolve_promise".to_string(), None, None)),
vec![
Expr::Var("__captured_promise".to_string(), None, None),
Expr::Var("value".to_string(), None, None),
],
))],
&{
let closure_env = new_js_object_data();
env_set(&closure_env, "__captured_promise", Value::Promise(promise)).unwrap();
closure_env
},
None,
);
Value::Closure(Rc::new(closure_data))
}
fn create_reject_function_direct(promise: Rc<RefCell<JSPromise>>) -> Value {
log::trace!("create_reject_function_direct called");
let closure_data = ClosureData::new(
&[DestructuringElement::Variable("reason".to_string(), None)],
&[stmt_expr(Expr::Call(
Box::new(Expr::Var("__internal_reject_promise".to_string(), None, None)),
vec![
Expr::Var("__captured_promise".to_string(), None, None),
Expr::Var("reason".to_string(), None, None),
],
))],
&{
let env = new_js_object_data();
env_set(&env, "__captured_promise", Value::Promise(promise)).unwrap();
env
},
None,
);
Value::Closure(Rc::new(closure_data))
}
pub fn handle_promise_then(promise_obj: &JSObjectDataPtr, args: &[crate::core::Expr], env: &JSObjectDataPtr) -> Result<Value, JSError> {
let promise_val = obj_get_key_value(promise_obj, &"__promise".into())?;
let promise = match promise_val {
Some(val_rc) => {
let val = val_rc.borrow();
match &*val {
Value::Promise(p) => p.clone(),
_ => {
return Err(raise_eval_error!("Invalid promise object"));
}
}
}
_ => {
return Err(raise_eval_error!("Invalid promise object"));
}
};
handle_promise_then_direct(promise, args, env)
}
pub fn handle_promise_then_direct(promise: Rc<RefCell<JSPromise>>, args: &[Expr], env: &JSObjectDataPtr) -> Result<Value, JSError> {
let new_promise = Rc::new(RefCell::new(JSPromise::new()));
let new_promise_obj = make_promise_object()?;
obj_set_key_value(&new_promise_obj, &"__promise".into(), Value::Promise(new_promise.clone()))?;
let on_fulfilled = if !args.is_empty() {
Some(evaluate_expr(env, &args[0])?)
} else {
None
};
let on_rejected = if args.len() > 1 {
Some(evaluate_expr(env, &args[1])?)
} else {
None
};
let mut promise_borrow = promise.borrow_mut();
if let Some(ref callback) = on_fulfilled {
promise_borrow.on_fulfilled.push((callback.clone(), new_promise.clone()));
} else {
let closure_data = ClosureData::new(
&[DestructuringElement::Variable("value".to_string(), None)],
&[stmt_expr(Expr::Call(
Box::new(Expr::Var("__internal_resolve_promise".to_string(), None, None)),
vec![
Expr::Var("__new_promise".to_string(), None, None),
Expr::Var("value".to_string(), None, None),
],
))],
&{
let env = new_js_object_data();
env_set(&env, "__new_promise", Value::Promise(new_promise.clone())).unwrap();
env
},
None,
);
let pass_through_fulfill = Value::Closure(Rc::new(closure_data));
promise_borrow.on_fulfilled.push((pass_through_fulfill, new_promise.clone()));
}
if let Some(ref callback) = on_rejected {
promise_borrow.on_rejected.push((callback.clone(), new_promise.clone()));
} else {
let closure_data = ClosureData::new(
&[DestructuringElement::Variable("reason".to_string(), None)],
&[stmt_expr(Expr::Call(
Box::new(Expr::Var("__internal_reject_promise".to_string(), None, None)),
vec![
Expr::Var("__new_promise".to_string(), None, None),
Expr::Var("reason".to_string(), None, None),
],
))],
&{
let env = new_js_object_data();
env_set(&env, "__new_promise", Value::Promise(new_promise.clone())).unwrap();
env
},
None,
);
let pass_through_reject = Value::Closure(Rc::new(closure_data));
promise_borrow.on_rejected.push((pass_through_reject, new_promise.clone()));
}
match &promise_borrow.state {
PromiseState::Fulfilled(val) => {
if let Some(ref callback) = on_fulfilled {
queue_task(Task::Resolution {
promise: promise.clone(),
callbacks: vec![(callback.clone(), new_promise.clone())],
});
} else {
resolve_promise(&new_promise, val.clone());
}
}
PromiseState::Rejected(val) => {
if let Some(ref callback) = on_rejected {
queue_task(Task::Rejection {
promise: promise.clone(),
callbacks: vec![(callback.clone(), new_promise.clone())],
});
} else {
reject_promise(&new_promise, val.clone());
}
}
_ => {}
}
Ok(Value::Object(new_promise_obj))
}
pub fn handle_promise_catch(promise_obj: &JSObjectDataPtr, args: &[crate::core::Expr], env: &JSObjectDataPtr) -> Result<Value, JSError> {
let promise_val = obj_get_key_value(promise_obj, &"__promise".into())?;
let promise = match promise_val {
Some(val_rc) => {
let val = val_rc.borrow();
match &*val {
Value::Promise(p) => p.clone(),
_ => {
return Err(raise_eval_error!("Invalid promise object"));
}
}
}
_ => {
return Err(raise_eval_error!("Invalid promise object"));
}
};
handle_promise_catch_direct(promise, args, env)
}
pub fn handle_promise_catch_direct(
promise: Rc<RefCell<JSPromise>>,
args: &[crate::core::Expr],
env: &JSObjectDataPtr,
) -> Result<Value, JSError> {
let new_promise = Rc::new(RefCell::new(JSPromise::new()));
let new_promise_obj = make_promise_object()?;
obj_set_key_value(&new_promise_obj, &"__promise".into(), Value::Promise(new_promise.clone()))?;
let on_rejected = if !args.is_empty() {
Some(evaluate_expr(env, &args[0])?)
} else {
None
};
let mut promise_borrow = promise.borrow_mut();
let closure_data = ClosureData::new(
&[DestructuringElement::Variable("value".to_string(), None)],
&[stmt_expr(Expr::Call(
Box::new(Expr::Var("__internal_resolve_promise".to_string(), None, None)),
vec![
Expr::Var("__new_promise".to_string(), None, None),
Expr::Var("value".to_string(), None, None),
],
))],
&{
let env = new_js_object_data();
env_set(&env, "__new_promise", Value::Promise(new_promise.clone())).unwrap();
env
},
None,
);
let pass_through_fulfill = Value::Closure(Rc::new(closure_data));
promise_borrow.on_fulfilled.push((pass_through_fulfill, new_promise.clone()));
if let Some(ref callback) = on_rejected {
promise_borrow.on_rejected.push((callback.clone(), new_promise.clone()));
} else {
let closure_data = ClosureData::new(
&[DestructuringElement::Variable("reason".to_string(), None)],
&[stmt_expr(Expr::Call(
Box::new(Expr::Var("__internal_reject_promise".to_string(), None, None)),
vec![
Expr::Var("__new_promise".to_string(), None, None),
Expr::Var("reason".to_string(), None, None),
],
))],
&{
let env = new_js_object_data();
env_set(&env, "__new_promise", Value::Promise(new_promise.clone())).unwrap();
env
},
None,
);
let pass_through_reject = Value::Closure(Rc::new(closure_data));
promise_borrow.on_rejected.push((pass_through_reject, new_promise.clone()));
}
match &promise_borrow.state {
PromiseState::Rejected(val) => {
if let Some(ref callback) = on_rejected {
queue_task(Task::Rejection {
promise: promise.clone(),
callbacks: vec![(callback.clone(), new_promise.clone())],
});
} else {
reject_promise(&new_promise, val.clone());
}
}
PromiseState::Fulfilled(val) => {
resolve_promise(&new_promise, val.clone());
}
_ => {}
}
Ok(Value::Object(new_promise_obj))
}
pub fn handle_promise_finally(promise_obj: &JSObjectDataPtr, args: &[crate::core::Expr], env: &JSObjectDataPtr) -> Result<Value, JSError> {
let promise_val = obj_get_key_value(promise_obj, &"__promise".into())?;
let promise = match promise_val {
Some(val_rc) => {
let val = val_rc.borrow();
match &*val {
Value::Promise(p) => p.clone(),
_ => {
return Err(raise_eval_error!("Invalid promise object"));
}
}
}
_ => {
return Err(raise_eval_error!("Invalid promise object"));
}
};
handle_promise_finally_direct(promise, args, env)
}
pub fn handle_promise_finally_direct(
promise: Rc<RefCell<JSPromise>>,
args: &[crate::core::Expr],
env: &JSObjectDataPtr,
) -> Result<Value, JSError> {
let new_promise = Rc::new(RefCell::new(JSPromise::new()));
let new_promise_obj = make_promise_object()?;
obj_set_key_value(&new_promise_obj, &"__promise".into(), Value::Promise(new_promise.clone()))?;
let on_finally = if !args.is_empty() {
Some(evaluate_expr(env, &args[0])?)
} else {
None
};
let closure_data = ClosureData::new(
&[DestructuringElement::Variable("value".to_string(), None)],
&[
stmt_expr(Expr::Call(Box::new(Expr::Var("finally_func".to_string(), None, None)), vec![])),
stmt_return(Some(Expr::Var("value".to_string(), None, None))),
],
&{
let new_env = env.clone();
if let Some(callback) = on_finally {
obj_set_key_value(&new_env, &"finally_func".into(), callback)?;
} else {
let closure_data = ClosureData::new(&[], &[], &new_env, None);
let noop = Value::Closure(Rc::new(closure_data));
obj_set_key_value(&new_env, &"finally_func".into(), noop)?;
}
new_env
},
None,
);
let finally_callback = Value::Closure(Rc::new(closure_data));
let mut promise_borrow = promise.borrow_mut();
promise_borrow.on_fulfilled.push((finally_callback.clone(), new_promise.clone()));
promise_borrow.on_rejected.push((finally_callback.clone(), new_promise.clone()));
match &promise_borrow.state {
PromiseState::Fulfilled(_) => {
queue_task(Task::Resolution {
promise: promise.clone(),
callbacks: vec![(finally_callback.clone(), new_promise.clone())],
});
}
PromiseState::Rejected(_) => {
queue_task(Task::Rejection {
promise: promise.clone(),
callbacks: vec![(finally_callback.clone(), new_promise.clone())],
});
}
_ => {}
}
Ok(Value::Object(new_promise_obj))
}
pub fn resolve_promise(promise: &Rc<RefCell<JSPromise>>, value: Value) {
log::trace!("resolve_promise called");
let mut promise_borrow = promise.borrow_mut();
if let PromiseState::Pending = promise_borrow.state {
if let Value::Object(obj) = &value
&& let Ok(Some(promise_val_rc)) = obj_get_key_value(obj, &"__promise".into())
&& let Value::Promise(other_promise) = &*promise_val_rc.borrow()
{
let current_promise = promise.clone();
let then_callback = Value::Closure(Rc::new(ClosureData::new(
&[DestructuringElement::Variable("val".to_string(), None)],
&[stmt_expr(Expr::Call(
Box::new(Expr::Var("__internal_resolve_promise".to_string(), None, None)),
vec![
Expr::Var("__current_promise".to_string(), None, None),
Expr::Var("val".to_string(), None, None),
],
))],
&{
let env = new_js_object_data();
env_set(&env, "__current_promise", Value::Promise(current_promise.clone())).unwrap();
env
},
None,
)));
let catch_callback = Value::Closure(Rc::new(ClosureData::new(
&[DestructuringElement::Variable("reason".to_string(), None)],
&[stmt_expr(Expr::Call(
Box::new(Expr::Var("__internal_reject_promise".to_string(), None, None)),
vec![
Expr::Var("__current_promise".to_string(), None, None),
Expr::Var("reason".to_string(), None, None),
],
))],
&{
let env = new_js_object_data();
env_set(&env, "__current_promise", Value::Promise(current_promise)).unwrap();
env
},
None,
)));
let other_promise_borrow = other_promise.borrow();
match &other_promise_borrow.state {
PromiseState::Fulfilled(val) => {
drop(promise_borrow);
resolve_promise(promise, val.clone());
return;
}
PromiseState::Rejected(reason) => {
drop(promise_borrow);
reject_promise(promise, reason.clone());
return;
}
PromiseState::Pending => {
drop(other_promise_borrow);
let mut other_promise_mut = other_promise.borrow_mut();
other_promise_mut.on_fulfilled.push((then_callback, promise.clone()));
other_promise_mut.on_rejected.push((catch_callback, promise.clone()));
return;
}
}
}
promise_borrow.state = PromiseState::Fulfilled(value.clone());
promise_borrow.value = Some(value);
let callbacks = promise_borrow.on_fulfilled.clone();
promise_borrow.on_fulfilled.clear();
if !callbacks.is_empty() {
log::trace!("resolve_promise: queuing {} callbacks", callbacks.len());
queue_task(Task::Resolution {
promise: promise.clone(),
callbacks,
});
}
}
}
pub fn reject_promise(promise: &Rc<RefCell<JSPromise>>, reason: Value) {
let mut promise_borrow = promise.borrow_mut();
if let Value::Object(obj) = &reason {
if let Ok(Some(ctor_rc)) = obj_get_key_value(obj, &"constructor".into()) {
log::debug!("reject_promise: rejecting with object whose constructor = {:?}", ctor_rc.borrow());
} else {
log::debug!("reject_promise: rejecting with object ptr={:p}", Rc::as_ptr(obj));
}
} else {
log::debug!("reject_promise: rejecting with value={}", value_to_string(&reason));
}
log::trace!("reject_promise callbacks count = {}", promise_borrow.on_rejected.len());
if let PromiseState::Pending = promise_borrow.state {
promise_borrow.state = PromiseState::Rejected(reason.clone());
promise_borrow.value = Some(reason.clone());
let callbacks = promise_borrow.on_rejected.clone();
promise_borrow.on_rejected.clear();
if !callbacks.is_empty() {
queue_task(Task::Rejection {
promise: promise.clone(),
callbacks,
});
} else {
log::trace!(
"reject_promise: queuing UnhandledCheck task for promise ptr={:p}",
Rc::as_ptr(promise)
);
queue_task(Task::UnhandledCheck {
promise: promise.clone(),
reason: reason.clone(),
});
}
}
}
#[allow(dead_code)]
pub fn is_promise(obj: &JSObjectDataPtr) -> bool {
if let Ok(Some(val_rc)) = obj_get_key_value(obj, &"__promise".into()) {
matches!(&*val_rc.borrow(), Value::Promise(_))
} else {
false
}
}
pub fn handle_promise_static_method(method: &str, args: &[crate::core::Expr], env: &JSObjectDataPtr) -> Result<Value, JSError> {
match method {
"all" => {
if args.is_empty() {
return Err(raise_eval_error!("Promise.all requires at least one argument"));
}
let iterable = evaluate_expr(env, &args[0])?;
let promises = match iterable {
Value::Object(arr) => {
let mut promises = Vec::new();
let mut i = 0;
loop {
let key = i.to_string();
if let Some(val) = obj_get_key_value(&arr, &key.into())? {
promises.push((*val).borrow().clone());
i += 1;
} else {
break;
}
}
promises
}
_ => {
return Err(raise_eval_error!("Promise.all argument must be iterable"));
}
};
let result_promise = Rc::new(RefCell::new(JSPromise::new()));
let result_promise_obj = make_promise_object()?;
obj_set_key_value(&result_promise_obj, &"__promise".into(), Value::Promise(result_promise.clone()))?;
let num_promises = promises.len();
if num_promises == 0 {
let result_arr = crate::js_array::create_array(env)?;
resolve_promise(&result_promise, Value::Object(result_arr));
return Ok(Value::Object(result_promise_obj));
}
let state_obj = new_js_object_data();
let results_obj = crate::js_array::create_array(env)?;
obj_set_key_value(&state_obj, &"results".into(), Value::Object(results_obj.clone()))?;
obj_set_key_value(&state_obj, &"completed".into(), Value::Number(0.0))?;
obj_set_key_value(&state_obj, &"total".into(), Value::Number(num_promises as f64))?;
obj_set_key_value(&state_obj, &"result_promise".into(), Value::Promise(result_promise.clone()))?;
for (idx, promise_val) in promises.into_iter().enumerate() {
let state_obj_clone = state_obj.clone();
match promise_val {
Value::Object(obj) => {
if let Some(promise_rc) = obj_get_key_value(&obj, &"__promise".into())? {
if let Value::Promise(promise_ref) = &*promise_rc.borrow() {
let promise_state = &promise_ref.borrow().state;
match promise_state {
PromiseState::Fulfilled(val) => {
obj_set_key_value(&results_obj, &idx.to_string().into(), val.clone())?;
if let Some(completed_val_rc) = obj_get_key_value(&state_obj, &"completed".into())?
&& let Value::Number(completed) = &*completed_val_rc.borrow()
{
let new_completed = completed + 1.0;
obj_set_key_value(&state_obj, &"completed".into(), Value::Number(new_completed))?;
if let Some(total_val_rc) = obj_get_key_value(&state_obj, &"total".into())?
&& let Value::Number(total) = &*total_val_rc.borrow()
&& new_completed == *total
{
if let Some(promise) = obj_get_key_value(&state_obj, &"result_promise".into())?
&& let Value::Promise(result_promise_ref) = &*promise.borrow()
{
resolve_promise(result_promise_ref, Value::Object(results_obj.clone()));
}
}
}
}
PromiseState::Rejected(reason) => {
if let Some(promise_val_rc) = obj_get_key_value(&state_obj, &"result_promise".into())?
&& let Value::Promise(result_promise_ref) = &*promise_val_rc.borrow()
{
reject_promise(result_promise_ref, reason.clone());
}
return Ok(Value::Object(result_promise_obj));
}
PromiseState::Pending => {
let then_callback = Value::Closure(Rc::new(ClosureData::new(
&[DestructuringElement::Variable("value".to_string(), None)],
&[stmt_expr(Expr::Call(
Box::new(Expr::Var("__internal_promise_all_resolve".to_string(), None, None)),
vec![
Expr::Number(idx as f64),
Expr::Var("value".to_string(), None, None),
Expr::Var("__state".to_string(), None, None),
],
))],
&{
let new_env = env.clone();
obj_set_key_value(&new_env, &"__state".into(), Value::Object(state_obj_clone.clone()))?;
new_env
},
None,
)));
let catch_callback = Value::Closure(Rc::new(ClosureData::new(
&[DestructuringElement::Variable("reason".to_string(), None)],
&[stmt_expr(Expr::Call(
Box::new(Expr::Var("__internal_promise_all_reject".to_string(), None, None)),
vec![
Expr::Var("reason".to_string(), None, None),
Expr::Var("__state".to_string(), None, None),
],
))],
&{
let new_env = env.clone();
obj_set_key_value(&new_env, &"__state".into(), Value::Object(state_obj_clone))?;
new_env
},
None,
)));
handle_promise_then(&obj, &[Expr::Value(then_callback)], env)?;
handle_promise_catch(&obj, &[Expr::Value(catch_callback)], env)?;
}
}
} else {
obj_set_key_value(&results_obj, &idx.to_string().into(), Value::Object(obj.clone()))?;
if let Some(completed_val_rc) = obj_get_key_value(&state_obj, &"completed".into())?
&& let Value::Number(completed) = &*completed_val_rc.borrow()
{
let new_completed = completed + 1.0;
obj_set_key_value(&state_obj, &"completed".into(), Value::Number(new_completed))?;
if let Some(total_val_rc) = obj_get_key_value(&state_obj, &"total".into())?
&& let Value::Number(total) = &*total_val_rc.borrow()
&& new_completed == *total
{
if let Some(promise_val_rc) = obj_get_key_value(&state_obj, &"result_promise".into())?
&& let Value::Promise(result_promise_ref) = &*promise_val_rc.borrow()
{
resolve_promise(result_promise_ref, Value::Object(results_obj.clone()));
}
}
}
}
} else {
obj_set_key_value(&results_obj, &idx.to_string().into(), Value::Object(obj.clone()))?;
if let Some(completed_val_rc) = obj_get_key_value(&state_obj, &"completed".into())?
&& let Value::Number(completed) = &*completed_val_rc.borrow()
{
let new_completed = completed + 1.0;
obj_set_key_value(&state_obj, &"completed".into(), Value::Number(new_completed))?;
if let Some(total_val_rc) = obj_get_key_value(&state_obj, &"total".into())?
&& let Value::Number(total) = &*total_val_rc.borrow()
&& new_completed == *total
{
if let Some(promise_val_rc) = obj_get_key_value(&state_obj, &"result_promise".into())?
&& let Value::Promise(result_promise_ref) = &*promise_val_rc.borrow()
{
resolve_promise(result_promise_ref, Value::Object(results_obj.clone()));
}
}
}
}
}
val => {
obj_set_key_value(&results_obj, &idx.to_string().into(), val.clone())?;
if let Some(completed_val_rc) = obj_get_key_value(&state_obj, &"completed".into())?
&& let Value::Number(completed) = &*completed_val_rc.borrow()
{
let new_completed = completed + 1.0;
obj_set_key_value(&state_obj, &"completed".into(), Value::Number(new_completed))?;
if let Some(total_val_rc) = obj_get_key_value(&state_obj, &"total".into())?
&& let Value::Number(total) = &*total_val_rc.borrow()
&& new_completed == *total
{
if let Some(promise_val_rc) = obj_get_key_value(&state_obj, &"result_promise".into())?
&& let Value::Promise(result_promise_ref) = &*promise_val_rc.borrow()
{
resolve_promise(result_promise_ref, Value::Object(results_obj.clone()));
}
}
}
}
}
}
Ok(Value::Object(result_promise_obj))
}
"allSettled" => {
if args.is_empty() {
return Err(raise_eval_error!("Promise.allSettled requires at least one argument"));
}
let iterable = evaluate_expr(env, &args[0])?;
let promises = match iterable {
Value::Object(arr) => {
let mut promises = Vec::new();
let mut i = 0;
loop {
let key = i.to_string();
if let Some(val) = obj_get_key_value(&arr, &key.into())? {
promises.push((*val).borrow().clone());
i += 1;
} else {
break;
}
}
promises
}
_ => {
return Err(raise_eval_error!("Promise.allSettled argument must be iterable"));
}
};
let result_promise = Rc::new(RefCell::new(JSPromise::new()));
let result_promise_obj = make_promise_object()?;
obj_set_key_value(&result_promise_obj, &"__promise".into(), Value::Promise(result_promise.clone()))?;
let num_promises = promises.len();
if num_promises == 0 {
let result_arr = crate::js_array::create_array(env)?;
resolve_promise(&result_promise, Value::Object(result_arr));
return Ok(Value::Object(result_promise_obj));
}
let state = Rc::new(RefCell::new(AllSettledState::new(
num_promises,
result_promise.clone(),
env.clone(),
)));
let state_index = ALLSETTLED_STATES.with(|states| {
let mut states_borrow = states.borrow_mut();
let index = states_borrow.len();
states_borrow.push(state.clone());
index
});
for (idx, promise_val) in promises.into_iter().enumerate() {
match promise_val {
Value::Object(obj) => {
if let Some(promise_rc) = obj_get_key_value(&obj, &"__promise".into())? {
if let Value::Promise(promise_ref) = &*promise_rc.borrow() {
let promise_state = &promise_ref.borrow().state;
match promise_state {
PromiseState::Fulfilled(val) => {
state.borrow_mut().record_fulfilled(idx, val.clone())?;
}
PromiseState::Rejected(reason) => {
state.borrow_mut().record_rejected(idx, reason.clone())?;
}
PromiseState::Pending => {
let then_callback = create_allsettled_resolve_callback(state_index, idx);
let catch_callback = create_allsettled_reject_callback(state_index, idx);
handle_promise_then(&obj, &[Expr::Value(then_callback)], env)?;
handle_promise_catch(&obj, &[Expr::Value(catch_callback)], env)?;
}
}
} else {
state.borrow_mut().record_fulfilled(idx, Value::Object(obj.clone()))?;
}
} else {
state.borrow_mut().record_fulfilled(idx, Value::Object(obj.clone()))?;
}
}
val => {
state.borrow_mut().record_fulfilled(idx, val)?;
}
}
}
Ok(Value::Object(result_promise_obj))
}
"any" => {
if args.is_empty() {
return Err(raise_eval_error!("Promise.any requires at least one argument"));
}
let iterable = evaluate_expr(env, &args[0])?;
let promises = match iterable {
Value::Object(arr) => {
let mut promises = Vec::new();
let mut i = 0;
loop {
let key = i.to_string();
if let Some(val) = obj_get_key_value(&arr, &key.into())? {
promises.push((*val).borrow().clone());
i += 1;
} else {
break;
}
}
promises
}
_ => {
return Err(raise_eval_error!("Promise.any argument must be iterable"));
}
};
let result_promise = Rc::new(RefCell::new(JSPromise::new()));
let result_promise_obj = make_promise_object()?;
obj_set_key_value(&result_promise_obj, &"__promise".into(), Value::Promise(result_promise.clone()))?;
let num_promises = promises.len();
if num_promises == 0 {
let aggregate_error = Rc::new(RefCell::new(crate::core::JSObjectData::new()));
obj_set_key_value(&aggregate_error, &"name".into(), Value::String(utf8_to_utf16("AggregateError")))?;
obj_set_key_value(
&aggregate_error,
&"message".into(),
Value::String(utf8_to_utf16("All promises were rejected")),
)?;
reject_promise(&result_promise, Value::Object(aggregate_error));
return Ok(Value::Object(result_promise_obj));
}
let rejections = Rc::new(RefCell::new(vec![None::<Value>; num_promises]));
let rejected_count = Rc::new(RefCell::new(0));
for (idx, promise_val) in promises.into_iter().enumerate() {
let _rejections_clone = rejections.clone();
let rejected_count_clone = rejected_count.clone();
let result_promise_clone = result_promise.clone();
match promise_val {
Value::Object(obj) => {
if let Some(promise_rc) = obj_get_key_value(&obj, &"__promise".into())? {
if let Value::Promise(_p) = &*promise_rc.borrow() {
let then_callback = Value::Closure(Rc::new(ClosureData::new(
&[DestructuringElement::Variable("value".to_string(), None)],
&[stmt_expr(Expr::Call(
Box::new(Expr::Var("__internal_promise_any_resolve".to_string(), None, None)),
vec![
Expr::Var("value".to_string(), None, None),
Expr::Var("__result_promise".to_string(), None, None),
],
))],
&{
let new_env = env.clone();
obj_set_key_value(
&new_env,
&"__result_promise".into(),
Value::Promise(result_promise_clone.clone()),
)?;
new_env
},
None,
)));
let catch_callback = Value::Closure(Rc::new(ClosureData::new(
&[DestructuringElement::Variable("reason".to_string(), None)],
&[stmt_expr(Expr::Call(
Box::new(Expr::Var("__internal_promise_any_reject".to_string(), None, None)),
vec![
Expr::Number(idx as f64),
Expr::Var("reason".to_string(), None, None),
Expr::Var("__rejections".to_string(), None, None),
Expr::Var("__rejected_count".to_string(), None, None),
Expr::Var("__total".to_string(), None, None),
Expr::Var("__result_promise".to_string(), None, None),
],
))],
&{
let new_env = env.clone();
obj_set_key_value(
&new_env,
&"__rejected_count".into(),
Value::Number(*rejected_count_clone.borrow() as f64),
)?;
obj_set_key_value(&new_env, &"__total".into(), Value::Number(num_promises as f64))?;
obj_set_key_value(&new_env, &"__result_promise".into(), Value::Promise(result_promise_clone))?;
new_env
},
None,
)));
handle_promise_then(&obj, &[Expr::Value(then_callback)], env)?;
handle_promise_catch(&obj, &[Expr::Value(catch_callback)], env)?;
} else {
resolve_promise(&result_promise, Value::Object(obj.clone()));
return Ok(Value::Object(result_promise_obj));
}
} else {
resolve_promise(&result_promise, Value::Object(obj.clone()));
return Ok(Value::Object(result_promise_obj));
}
}
val => {
resolve_promise(&result_promise, val.clone());
return Ok(Value::Object(result_promise_obj));
}
}
}
Ok(Value::Object(result_promise_obj))
}
"race" => {
if args.is_empty() {
return Err(raise_eval_error!("Promise.race requires at least one argument"));
}
let iterable = evaluate_expr(env, &args[0])?;
let promises = match iterable {
Value::Object(arr) => {
let mut promises = Vec::new();
let mut i = 0;
loop {
let key = i.to_string();
if let Some(val) = obj_get_key_value(&arr, &key.into())? {
promises.push((*val).borrow().clone());
i += 1;
} else {
break;
}
}
promises
}
_ => {
return Err(raise_eval_error!("Promise.race argument must be iterable"));
}
};
let result_promise = Rc::new(RefCell::new(JSPromise::new()));
let result_promise_obj = make_promise_object()?;
obj_set_key_value(&result_promise_obj, &"__promise".into(), Value::Promise(result_promise.clone()))?;
for promise_val in promises {
let result_promise_clone = result_promise.clone();
match promise_val {
Value::Object(obj) => {
if let Some(promise_rc) = obj_get_key_value(&obj, &"__promise".into())? {
if let Value::Promise(promise_ref) = &*promise_rc.borrow() {
let promise_state = &promise_ref.borrow().state;
match promise_state {
PromiseState::Fulfilled(val) => {
resolve_promise(&result_promise, val.clone());
return Ok(Value::Object(result_promise_obj));
}
PromiseState::Rejected(reason) => {
reject_promise(&result_promise, reason.clone());
return Ok(Value::Object(result_promise_obj));
}
PromiseState::Pending => {
let then_callback = Value::Closure(Rc::new(ClosureData::new(
&[DestructuringElement::Variable("value".to_string(), None)],
&[stmt_expr(Expr::Call(
Box::new(Expr::Var("__internal_promise_race_resolve".to_string(), None, None)),
vec![
Expr::Var("value".to_string(), None, None),
Expr::Var("__result_promise".to_string(), None, None),
],
))],
&{
let new_env = env.clone();
obj_set_key_value(
&new_env,
&"__result_promise".into(),
Value::Promise(result_promise_clone.clone()),
)?;
new_env
},
None,
)));
let catch_callback = Value::Closure(Rc::new(ClosureData::new(
&[DestructuringElement::Variable("reason".to_string(), None)],
&[stmt_expr(Expr::Call(
Box::new(Expr::Var("__internal_promise_race_reject".to_string(), None, None)),
vec![
Expr::Var("reason".to_string(), None, None),
Expr::Var("__result_promise".to_string(), None, None),
],
))],
&{
let new_env = env.clone();
obj_set_key_value(
&new_env,
&"__result_promise".into(),
Value::Promise(result_promise_clone),
)?;
new_env
},
None,
)));
handle_promise_then(&obj, &[Expr::Value(then_callback)], env)?;
handle_promise_catch(&obj, &[Expr::Value(catch_callback)], env)?;
}
}
} else {
resolve_promise(&result_promise, Value::Object(obj.clone()));
return Ok(Value::Object(result_promise_obj));
}
} else {
resolve_promise(&result_promise, Value::Object(obj.clone()));
return Ok(Value::Object(result_promise_obj));
}
}
val => {
resolve_promise(&result_promise, val.clone());
return Ok(Value::Object(result_promise_obj));
}
}
}
Ok(Value::Object(result_promise_obj))
}
"resolve" => {
let value = if args.is_empty() {
Value::Undefined
} else {
evaluate_expr(env, &args[0])?
};
if let Value::Object(obj) = &value
&& let Some(promise_rc) = obj_get_key_value(obj, &"__promise".into())?
&& let Value::Promise(_) = &*promise_rc.borrow()
{
return Ok(Value::Object(obj.clone()));
}
let result_promise = Rc::new(RefCell::new(JSPromise::new()));
{
let mut p = result_promise.borrow_mut();
p.state = PromiseState::Fulfilled(value.clone());
p.value = Some(value.clone());
}
let result_promise_obj = make_promise_object()?;
obj_set_key_value(&result_promise_obj, &"__promise".into(), Value::Promise(result_promise.clone()))?;
Ok(Value::Object(result_promise_obj))
}
"reject" => {
let reason = if args.is_empty() {
Value::Undefined
} else {
evaluate_expr(env, &args[0])?
};
let result_promise = Rc::new(RefCell::new(JSPromise::new()));
{
let mut p = result_promise.borrow_mut();
p.state = PromiseState::Rejected(reason.clone());
p.value = Some(reason.clone());
}
let result_promise_obj = make_promise_object()?;
obj_set_key_value(&result_promise_obj, &"__promise".into(), Value::Promise(result_promise.clone()))?;
Ok(Value::Object(result_promise_obj))
}
_ => Err(raise_eval_error!(format!("Promise has no static method '{method}'"))),
}
}
pub fn handle_promise_method(obj_map: &JSObjectDataPtr, method: &str, args: &[Expr], env: &JSObjectDataPtr) -> Result<Value, JSError> {
match method {
"then" => crate::js_promise::handle_promise_then(obj_map, args, env),
"catch" => crate::js_promise::handle_promise_catch(obj_map, args, env),
"finally" => crate::js_promise::handle_promise_finally(obj_map, args, env),
_ => Err(raise_eval_error!(format!("Promise has no method '{method}'"))),
}
}
pub fn __internal_promise_allsettled_resolve(idx: f64, value: Value, shared_state: Value) -> Result<(), JSError> {
if let Value::Object(shared_state_obj) = shared_state {
if let Some(results_val_rc) = obj_get_key_value(&shared_state_obj, &"results".into())?
&& let Value::Object(results_obj) = &*results_val_rc.borrow()
{
let settled = Rc::new(RefCell::new(crate::core::JSObjectData::new()));
obj_set_key_value(&settled, &"status".into(), Value::String(utf8_to_utf16("fulfilled")))?;
obj_set_key_value(&settled, &"value".into(), value)?;
obj_set_key_value(results_obj, &idx.to_string().into(), Value::Object(settled))?;
}
if let Some(completed_val_rc) = obj_get_key_value(&shared_state_obj, &"completed".into())?
&& let Value::Number(completed) = &*completed_val_rc.borrow()
{
let new_completed = completed + 1.0;
obj_set_key_value(&shared_state_obj, &"completed".into(), Value::Number(new_completed))?;
if let Some(total_val_rc) = obj_get_key_value(&shared_state_obj, &"total".into())?
&& let Value::Number(total) = &*total_val_rc.borrow()
&& new_completed == *total
{
if let Some(promise_val_rc) = obj_get_key_value(&shared_state_obj, &"result_promise".into())?
&& let Value::Promise(result_promise) = &*promise_val_rc.borrow()
&& let Some(results_val_rc) = obj_get_key_value(&shared_state_obj, &"results".into())?
&& let Value::Object(results_obj) = &*results_val_rc.borrow()
{
resolve_promise(result_promise, Value::Object(results_obj.clone()));
}
}
}
}
Ok(())
}
pub fn __internal_promise_allsettled_reject(idx: f64, reason: Value, shared_state: Value) -> Result<(), JSError> {
if let Value::Object(shared_state_obj) = shared_state {
if let Some(results_val_rc) = obj_get_key_value(&shared_state_obj, &"results".into())?
&& let Value::Object(results_obj) = &*results_val_rc.borrow()
{
let settled = Rc::new(RefCell::new(crate::core::JSObjectData::new()));
obj_set_key_value(&settled, &"status".into(), Value::String(utf8_to_utf16("rejected")))?;
obj_set_key_value(&settled, &"reason".into(), reason)?;
obj_set_key_value(results_obj, &idx.to_string().into(), Value::Object(settled))?;
}
if let Some(completed_val_rc) = obj_get_key_value(&shared_state_obj, &"completed".into())?
&& let Value::Number(completed) = &*completed_val_rc.borrow()
{
let new_completed = completed + 1.0;
obj_set_key_value(&shared_state_obj, &"completed".into(), Value::Number(new_completed))?;
if let Some(total_val_rc) = obj_get_key_value(&shared_state_obj, &"total".into())?
&& let Value::Number(total) = &*total_val_rc.borrow()
&& new_completed == *total
{
if let Some(promise_val_rc) = obj_get_key_value(&shared_state_obj, &"result_promise".into())?
&& let Value::Promise(result_promise) = &*promise_val_rc.borrow()
&& let Some(results_val_rc) = obj_get_key_value(&shared_state_obj, &"results".into())?
&& let Value::Object(results_obj) = &*results_val_rc.borrow()
{
resolve_promise(result_promise, Value::Object(results_obj.clone()));
}
}
}
}
Ok(())
}
pub fn __internal_promise_any_resolve(value: Value, result_promise: Rc<RefCell<JSPromise>>) {
resolve_promise(&result_promise, value);
}
pub fn __internal_promise_any_reject(
idx: f64,
reason: Value,
rejections: Rc<RefCell<Vec<Option<Value>>>>,
rejected_count: Rc<RefCell<usize>>,
total: usize,
result_promise: Rc<RefCell<JSPromise>>,
) -> Result<(), JSError> {
let idx = idx as usize;
rejections.borrow_mut()[idx] = Some(reason);
*rejected_count.borrow_mut() += 1;
if *rejected_count.borrow() == total {
let aggregate_error = Rc::new(RefCell::new(crate::core::JSObjectData::new()));
obj_set_key_value(&aggregate_error, &"name".into(), Value::String(utf8_to_utf16("AggregateError"))).unwrap();
obj_set_key_value(
&aggregate_error,
&"message".into(),
Value::String(utf8_to_utf16("All promises were rejected")),
)?;
let errors_array = Rc::new(RefCell::new(crate::core::JSObjectData::new()));
let rejections_vec = rejections.borrow();
for (i, rejection) in rejections_vec.iter().enumerate() {
if let Some(err) = rejection {
obj_set_key_value(&errors_array, &i.to_string().into(), err.clone())?;
}
}
obj_set_key_value(&aggregate_error, &"errors".into(), Value::Object(errors_array))?;
reject_promise(&result_promise, Value::Object(aggregate_error));
}
Ok(())
}
pub fn __internal_promise_race_resolve(value: Value, result_promise: Rc<RefCell<JSPromise>>) {
resolve_promise(&result_promise, value);
}
pub fn __internal_promise_race_reject(reason: Value, result_promise: Rc<RefCell<JSPromise>>) {
reject_promise(&result_promise, reason);
}
pub fn __internal_allsettled_state_record_fulfilled(state_index: f64, index: f64, value: Value) -> Result<(), JSError> {
log::trace!("__internal_allsettled_state_record_fulfilled called: state_idx={state_index}, idx={index}, val={value:?}");
let state_index = state_index as usize;
let index = index as usize;
ALLSETTLED_STATES.with(|states| {
let states_borrow = states.borrow();
if state_index < states_borrow.len() {
let state = &states_borrow[state_index];
log::trace!("Recording fulfilled for index {} in state {}", index, state_index);
state.borrow_mut().record_fulfilled(index, value)?;
} else {
log::trace!("Invalid state_index {} (len={})", state_index, states_borrow.len());
}
Ok::<(), JSError>(())
})?;
Ok(())
}
pub fn __internal_allsettled_state_record_rejected(state_index: f64, index: f64, reason: Value) -> Result<(), JSError> {
log::trace!("__internal_allsettled_state_record_rejected called: state_index={state_index}, index={index}, reason={reason:?}");
let state_index = state_index as usize;
let index = index as usize;
ALLSETTLED_STATES.with(|states| {
let states_borrow = states.borrow();
if state_index < states_borrow.len() {
let state = &states_borrow[state_index];
log::trace!("Recording rejected for index {} in state {}", index, state_index);
state.borrow_mut().record_rejected(index, reason)?;
} else {
log::trace!("Invalid state_index {} (len={})", state_index, states_borrow.len());
}
Ok::<(), JSError>(())
})?;
Ok(())
}
fn create_allsettled_resolve_callback(state_index: usize, index: usize) -> Value {
Value::Closure(Rc::new(ClosureData::new(
&[DestructuringElement::Variable("value".to_string(), None)],
&[stmt_expr(Expr::Call(
Box::new(Expr::Var("__internal_allsettled_state_record_fulfilled".to_string(), None, None)),
vec![
Expr::Number(state_index as f64),
Expr::Number(index as f64),
Expr::Var("value".to_string(), None, None),
],
))],
&Rc::new(RefCell::new(crate::core::JSObjectData::new())), None,
)))
}
fn create_allsettled_reject_callback(state_index: usize, index: usize) -> Value {
Value::Closure(Rc::new(ClosureData::new(
&[DestructuringElement::Variable("reason".to_string(), None)],
&[stmt_expr(Expr::Call(
Box::new(Expr::Var("__internal_allsettled_state_record_rejected".to_string(), None, None)),
vec![
Expr::Number(state_index as f64),
Expr::Number(index as f64),
Expr::Var("reason".to_string(), None, None),
],
))],
&Rc::new(RefCell::new(crate::core::JSObjectData::new())), None,
)))
}
pub fn handle_set_timeout(args: &[Expr], env: &JSObjectDataPtr) -> Result<Value, JSError> {
if args.is_empty() {
return Err(raise_eval_error!("setTimeout requires at least one argument"));
}
let callback = evaluate_expr(env, &args[0])?;
let delay = if args.len() > 1 {
match evaluate_expr(env, &args[1])? {
Value::Number(n) => n.max(0.0) as u64,
_ => 0,
}
} else {
0
};
let mut timeout_args = Vec::new();
for arg in args.iter().skip(2) {
timeout_args.push(evaluate_expr(env, arg)?);
}
let id = NEXT_TIMEOUT_ID.with(|counter| {
let mut id = counter.borrow_mut();
let current_id = *id;
*id += 1;
current_id
});
queue_task(Task::Timeout {
id,
callback,
args: timeout_args,
target_time: Instant::now() + Duration::from_millis(delay),
});
Ok(Value::Number(id as f64))
}
pub fn handle_clear_timeout(args: &[Expr], env: &JSObjectDataPtr) -> Result<Value, JSError> {
if args.is_empty() {
return Ok(Value::Undefined);
}
let id_val = evaluate_expr(env, &args[0])?;
let id = match id_val {
Value::Number(n) => n as usize,
_ => return Ok(Value::Undefined),
};
GLOBAL_TASK_QUEUE.with(|queue| {
let mut queue_borrow = queue.borrow_mut();
queue_borrow.retain(|task| !matches!(task, Task::Timeout { id: task_id, .. } if *task_id == id));
});
Ok(Value::Undefined)
}
pub fn handle_set_interval(args: &[Expr], env: &JSObjectDataPtr) -> Result<Value, JSError> {
if args.is_empty() {
return Err(raise_eval_error!("setInterval requires at least one argument"));
}
let callback = evaluate_expr(env, &args[0])?;
let delay = if args.len() > 1 {
match evaluate_expr(env, &args[1])? {
Value::Number(n) => n.max(0.0) as u64,
_ => 0,
}
} else {
0
};
let mut interval_args = Vec::new();
for arg in args.iter().skip(2) {
interval_args.push(evaluate_expr(env, arg)?);
}
let id = NEXT_TIMEOUT_ID.with(|counter| {
let mut id = counter.borrow_mut();
let current_id = *id;
*id += 1;
current_id
});
let interval = Duration::from_millis(delay);
queue_task(Task::Interval {
id,
callback,
args: interval_args,
target_time: Instant::now() + interval,
interval,
});
Ok(Value::Number(id as f64))
}
pub fn handle_clear_interval(args: &[Expr], env: &JSObjectDataPtr) -> Result<Value, JSError> {
if args.is_empty() {
return Ok(Value::Undefined);
}
let id_val = evaluate_expr(env, &args[0])?;
let id = match id_val {
Value::Number(n) => n as usize,
_ => return Ok(Value::Undefined),
};
GLOBAL_TASK_QUEUE.with(|queue| {
let mut queue_borrow = queue.borrow_mut();
queue_borrow.retain(|task| !matches!(task, Task::Interval { id: task_id, .. } if *task_id == id));
});
Ok(Value::Undefined)
}