use crate::host::HostFunction;
use crate::object::function::JSFunction;
use crate::object::object::JSObject;
use crate::runtime::context::JSContext;
use crate::value::JSValue;
use std::collections::VecDeque;
const PROMISE_STATE_SLOT: &str = "__promise_state__";
const PROMISE_RESULT_SLOT: &str = "__promise_result__";
const PROMISE_REACTIONS_SLOT: &str = "__promise_reactions__";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PromiseState {
Pending,
Fulfilled,
Rejected,
}
#[derive(Clone)]
pub struct Reaction {
pub handler: JSValue,
pub is_reject: bool,
pub is_all_settled: bool,
pub target_promise: JSValue,
}
pub struct MicrotaskQueue {
queue: VecDeque<Microtask>,
}
pub enum Microtask {
Reaction(Reaction, JSValue),
UserCallback(JSValue, Vec<JSValue>),
}
impl MicrotaskQueue {
pub fn new() -> Self {
MicrotaskQueue {
queue: VecDeque::new(),
}
}
pub fn enqueue(&mut self, task: Microtask) {
self.queue.push_back(task);
}
pub fn is_empty(&self) -> bool {
self.queue.is_empty()
}
pub fn dequeue(&mut self) -> Option<Microtask> {
self.queue.pop_front()
}
}
impl Default for MicrotaskQueue {
fn default() -> Self {
Self::new()
}
}
fn create_builtin_function(ctx: &mut JSContext, name: &str) -> JSValue {
let mut func = JSFunction::new_builtin(ctx.intern(name), 1);
func.set_builtin_marker(ctx, name);
let ptr = Box::into_raw(Box::new(func)) as usize;
ctx.runtime_mut().gc_heap_mut().track_function(ptr);
JSValue::new_function(ptr)
}
fn intern_str(ctx: &mut JSContext, s: &str) -> crate::runtime::atom::Atom {
ctx.intern(s)
}
fn get_promise_state(ctx: &mut JSContext, obj: &JSObject) -> PromiseState {
let state_atom = intern_str(ctx, PROMISE_STATE_SLOT);
if let Some(state_val) = obj.get(state_atom) {
let val = state_val.get_int();
match val {
1 => PromiseState::Fulfilled,
2 => PromiseState::Rejected,
_ => PromiseState::Pending,
}
} else {
PromiseState::Pending
}
}
fn set_promise_state(ctx: &mut JSContext, obj: &mut JSObject, state: PromiseState) {
let state_val = match state {
PromiseState::Pending => JSValue::new_int(0),
PromiseState::Fulfilled => JSValue::new_int(1),
PromiseState::Rejected => JSValue::new_int(2),
};
let state_atom = intern_str(ctx, PROMISE_STATE_SLOT);
obj.set(state_atom, state_val);
}
fn get_promise_result(ctx: &mut JSContext, obj: &JSObject) -> JSValue {
let result_atom = intern_str(ctx, PROMISE_RESULT_SLOT);
obj.get(result_atom).unwrap_or(JSValue::undefined())
}
fn set_promise_result(ctx: &mut JSContext, obj: &mut JSObject, value: JSValue) {
let result_atom = intern_str(ctx, PROMISE_RESULT_SLOT);
obj.set(result_atom, value);
}
fn get_promise_reactions(ctx: &mut JSContext, obj: &JSObject) -> Vec<Reaction> {
let reactions_atom = intern_str(ctx, PROMISE_REACTIONS_SLOT);
if let Some(reactions_val) = obj.get(reactions_atom) {
if reactions_val.is_object() {
let ptr = reactions_val.get_ptr();
if ptr == 0 {
return Vec::new();
}
let arr = reactions_val.as_object();
let len_atom = intern_str(ctx, "length");
let len = arr.get(len_atom).map(|v| v.get_int() as usize).unwrap_or(0);
let mut reactions = Vec::new();
for i in 0..len {
let idx_atom = intern_str(ctx, &i.to_string());
if let Some(r_val) = arr.get(idx_atom) {
if r_val.is_object() {
let r_obj = r_val.as_object();
let handler_atom = intern_str(ctx, "handler");
let is_reject_atom = intern_str(ctx, "isReject");
if let Some(handler) = r_obj.get(handler_atom) {
let is_reject = r_obj
.get(is_reject_atom)
.map(|v| v.get_bool())
.unwrap_or(false);
reactions.push(Reaction {
handler,
is_reject,
is_all_settled: false,
target_promise: JSValue::undefined(),
});
}
}
}
}
return reactions;
}
}
Vec::new()
}
fn create_reaction_array(ctx: &mut JSContext, reactions: Vec<Reaction>) -> JSValue {
let mut arr = JSObject::new_array();
let len_atom = intern_str(ctx, "length");
arr.set(len_atom, JSValue::new_int(reactions.len() as i64));
for (i, reaction) in reactions.into_iter().enumerate() {
let mut r_obj = JSObject::new();
let handler_atom = intern_str(ctx, "handler");
let is_reject_atom = intern_str(ctx, "isReject");
r_obj.set(handler_atom, reaction.handler);
r_obj.set(is_reject_atom, JSValue::bool(reaction.is_reject));
let r_ptr = Box::into_raw(Box::new(r_obj)) as usize;
let idx_atom = intern_str(ctx, &i.to_string());
arr.set(idx_atom, JSValue::new_object(r_ptr));
}
let ptr = Box::into_raw(Box::new(arr)) as usize;
JSValue::new_object(ptr)
}
pub fn is_promise(value: &JSValue) -> bool {
if !value.is_object() {
return false;
}
let obj = value.as_object();
obj.is_promise()
}
fn create_promise(ctx: &mut JSContext) -> JSObject {
let mut promise = JSObject::new_promise();
if let Some(proto_ptr) = ctx.get_promise_prototype() {
promise.prototype = Some(proto_ptr);
}
let state_atom = intern_str(ctx, PROMISE_STATE_SLOT);
let result_atom = intern_str(ctx, PROMISE_RESULT_SLOT);
let reactions_atom = intern_str(ctx, PROMISE_REACTIONS_SLOT);
promise.set(state_atom, JSValue::new_int(0));
promise.set(result_atom, JSValue::undefined());
promise.set(reactions_atom, JSValue::null());
promise
}
fn fulfill_promise(ctx: &mut JSContext, promise: &mut JSObject, value: JSValue) {
set_promise_state(ctx, promise, PromiseState::Fulfilled);
set_promise_result(ctx, promise, value);
process_reactions(ctx, promise, PromiseState::Fulfilled);
}
fn reject_promise(ctx: &mut JSContext, promise: &mut JSObject, reason: JSValue) {
set_promise_state(ctx, promise, PromiseState::Rejected);
set_promise_result(ctx, promise, reason);
process_reactions(ctx, promise, PromiseState::Rejected);
}
fn process_reactions(ctx: &mut JSContext, promise: &mut JSObject, state: PromiseState) {
let reactions = get_promise_reactions(ctx, promise);
for reaction in reactions {
let argument = get_promise_result(ctx, promise);
let microtask = if reaction.is_reject && state == PromiseState::Rejected {
Microtask::Reaction(reaction, argument)
} else if !reaction.is_reject && state == PromiseState::Fulfilled {
Microtask::Reaction(reaction, argument)
} else {
continue;
};
ctx.microtask_enqueue(microtask);
}
}
fn promise_resolve(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
let value = if args.len() >= 2 {
args[1]
} else if !args.is_empty() {
args[0]
} else {
JSValue::undefined()
};
if value.is_object() {
let obj = value.as_object();
if obj.is_promise() {
return value;
}
}
let mut promise = create_promise(ctx);
fulfill_promise(ctx, &mut promise, value);
let ptr = Box::into_raw(Box::new(promise)) as usize;
JSValue::new_object(ptr)
}
fn promise_reject(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
let reason = if args.len() >= 2 {
args[1]
} else if !args.is_empty() {
args[0]
} else {
JSValue::undefined()
};
let mut promise = create_promise(ctx);
reject_promise(ctx, &mut promise, reason);
let ptr = Box::into_raw(Box::new(promise)) as usize;
JSValue::new_object(ptr)
}
fn promise_then(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
if args.is_empty() {
return JSValue::undefined();
}
let this_val = &args[0];
if !this_val.is_object() {
return JSValue::undefined();
}
let promise = this_val.as_object_mut();
if !promise.is_promise() {
return JSValue::undefined();
}
let on_fulfilled = args.get(1).copied().unwrap_or(JSValue::undefined());
let on_rejected = args.get(2).copied().unwrap_or(JSValue::undefined());
let new_promise = create_promise(ctx);
let ptr_new = Box::into_raw(Box::new(new_promise)) as usize;
let target = JSValue::new_object(ptr_new);
let state = get_promise_state(ctx, promise);
let reactions_atom = intern_str(ctx, PROMISE_REACTIONS_SLOT);
match state {
PromiseState::Fulfilled => {
let result = get_promise_result(ctx, promise);
let reaction = Reaction {
handler: on_fulfilled,
is_reject: false,
is_all_settled: false,
target_promise: target,
};
ctx.microtask_enqueue(Microtask::Reaction(reaction, result));
}
PromiseState::Rejected => {
let reason = get_promise_result(ctx, promise);
let reaction = Reaction {
handler: on_rejected,
is_reject: true,
is_all_settled: false,
target_promise: target,
};
ctx.microtask_enqueue(Microtask::Reaction(reaction, reason));
}
PromiseState::Pending => {
let mut reactions = get_promise_reactions(ctx, promise);
reactions.push(Reaction {
handler: on_fulfilled,
is_reject: false,
is_all_settled: false,
target_promise: target,
});
reactions.push(Reaction {
handler: on_rejected,
is_reject: true,
is_all_settled: false,
target_promise: target,
});
let reactions_arr = create_reaction_array(ctx, reactions);
promise.set(reactions_atom, reactions_arr);
}
}
target
}
fn promise_catch(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
if args.is_empty() {
return JSValue::undefined();
}
let this_val = &args[0];
let on_rejected = args.get(1).copied().unwrap_or(JSValue::undefined());
if !this_val.is_object() {
return JSValue::undefined();
}
let promise = this_val.as_object_mut();
if !promise.is_promise() {
return JSValue::undefined();
}
let new_promise = create_promise(ctx);
let ptr_new = Box::into_raw(Box::new(new_promise)) as usize;
let target = JSValue::new_object(ptr_new);
let state = get_promise_state(ctx, promise);
let reactions_atom = intern_str(ctx, PROMISE_REACTIONS_SLOT);
match state {
PromiseState::Fulfilled => {
let result = get_promise_result(ctx, promise);
let reaction = Reaction {
handler: on_rejected,
is_reject: false,
is_all_settled: false,
target_promise: target,
};
ctx.microtask_enqueue(Microtask::Reaction(reaction, result));
}
PromiseState::Rejected => {
let reason = get_promise_result(ctx, promise);
let reaction = Reaction {
handler: on_rejected,
is_reject: true,
is_all_settled: false,
target_promise: target,
};
ctx.microtask_enqueue(Microtask::Reaction(reaction, reason));
}
PromiseState::Pending => {
let mut reactions = get_promise_reactions(ctx, promise);
reactions.push(Reaction {
handler: on_rejected,
is_reject: true,
is_all_settled: false,
target_promise: target,
});
let reactions_arr = create_reaction_array(ctx, reactions);
promise.set(reactions_atom, reactions_arr);
}
}
target
}
fn promise_finally(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
if args.is_empty() {
return JSValue::undefined();
}
let this_val = &args[0];
let on_finally = args.get(1).copied().unwrap_or(JSValue::undefined());
if !this_val.is_object() {
return JSValue::undefined();
}
let promise = this_val.as_object_mut();
if !promise.is_promise() {
return JSValue::undefined();
}
let new_promise = create_promise(ctx);
let ptr_new = Box::into_raw(Box::new(new_promise)) as usize;
let target = JSValue::new_object(ptr_new);
let state = get_promise_state(ctx, promise);
let reactions_atom = intern_str(ctx, PROMISE_REACTIONS_SLOT);
match state {
PromiseState::Fulfilled => {
let result = get_promise_result(ctx, promise);
let reaction = Reaction {
handler: on_finally,
is_reject: false,
is_all_settled: false,
target_promise: target,
};
ctx.microtask_enqueue(Microtask::Reaction(reaction, result));
}
PromiseState::Rejected => {
let reason = get_promise_result(ctx, promise);
let reaction = Reaction {
handler: on_finally,
is_reject: true,
is_all_settled: false,
target_promise: target,
};
ctx.microtask_enqueue(Microtask::Reaction(reaction, reason));
}
PromiseState::Pending => {
let mut reactions = get_promise_reactions(ctx, promise);
reactions.push(Reaction {
handler: on_finally,
is_reject: false,
is_all_settled: false,
target_promise: target,
});
reactions.push(Reaction {
handler: on_finally,
is_reject: true,
is_all_settled: false,
target_promise: target,
});
let reactions_arr = create_reaction_array(ctx, reactions);
promise.set(reactions_atom, reactions_arr);
}
}
target
}
fn promise_all(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
let iterable = args.get(0).copied().unwrap_or(JSValue::undefined());
let result_promise = create_promise(ctx);
let result_promise_ptr = Box::into_raw(Box::new(result_promise)) as usize;
let result_promise_val = JSValue::new_object(result_promise_ptr);
if !iterable.is_object() {
let rp = result_promise_val.as_object_mut();
let err_val =
JSValue::new_string(ctx.intern("TypeError: Promise.all requires an iterable"));
set_promise_result(ctx, rp, err_val);
set_promise_state(ctx, rp, PromiseState::Rejected);
return result_promise_val;
}
let obj = iterable.as_object();
let length_atom = intern_str(ctx, "length");
let len_val = obj.get(length_atom).unwrap_or(JSValue::undefined());
if !len_val.is_int() {
let rp = result_promise_val.as_object_mut();
let err_val =
JSValue::new_string(ctx.intern("TypeError: Promise.all requires an iterable"));
set_promise_result(ctx, rp, err_val);
set_promise_state(ctx, rp, PromiseState::Rejected);
return result_promise_val;
}
let len = len_val.get_int() as usize;
let pending_atom = ctx.intern("__promise_all_pending__");
let result_promise_mut = result_promise_val.as_object_mut();
result_promise_mut.set(pending_atom, JSValue::new_int(len as i64));
for i in 0..len {
let idx_atom = ctx.intern(&i.to_string());
let item = obj.get(idx_atom).unwrap_or(JSValue::undefined());
let reaction = Reaction {
handler: JSValue::new_int(i as i64),
is_reject: false,
is_all_settled: false,
target_promise: JSValue::undefined(),
};
ctx.microtask_enqueue(Microtask::Reaction(reaction, item));
}
result_promise_val
}
fn promise_race(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
let iterable = args.get(0).copied().unwrap_or(JSValue::undefined());
let result_promise = create_promise(ctx);
let result_promise_ptr = Box::into_raw(Box::new(result_promise)) as usize;
let result_promise_val = JSValue::new_object(result_promise_ptr);
if !iterable.is_object() {
let rp = result_promise_val.as_object_mut();
let err_val =
JSValue::new_string(ctx.intern("TypeError: Promise.race requires an iterable"));
set_promise_result(ctx, rp, err_val);
set_promise_state(ctx, rp, PromiseState::Rejected);
return result_promise_val;
}
let obj = iterable.as_object();
let length_atom = intern_str(ctx, "length");
let len_val = obj.get(length_atom).unwrap_or(JSValue::undefined());
if !len_val.is_int() {
let rp = result_promise_val.as_object_mut();
let err_val =
JSValue::new_string(ctx.intern("TypeError: Promise.race requires an iterable"));
set_promise_result(ctx, rp, err_val);
set_promise_state(ctx, rp, PromiseState::Rejected);
return result_promise_val;
}
let len = len_val.get_int() as usize;
if len == 0 {
return result_promise_val;
}
for i in 0..len {
let idx_atom = ctx.intern(&i.to_string());
let item = obj.get(idx_atom).unwrap_or(JSValue::undefined());
let reaction = Reaction {
handler: JSValue::new_int(i as i64),
is_reject: false,
is_all_settled: false,
target_promise: JSValue::undefined(),
};
ctx.microtask_enqueue(Microtask::Reaction(reaction, item));
}
result_promise_val
}
fn promise_all_settled(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
let iterable = args.get(0).copied().unwrap_or(JSValue::undefined());
let result_promise = create_promise(ctx);
let result_promise_ptr = Box::into_raw(Box::new(result_promise)) as usize;
let result_promise_val = JSValue::new_object(result_promise_ptr);
if !iterable.is_object() {
let rp = result_promise_val.as_object_mut();
let err_val =
JSValue::new_string(ctx.intern("TypeError: Promise.allSettled requires an iterable"));
set_promise_result(ctx, rp, err_val);
set_promise_state(ctx, rp, PromiseState::Rejected);
return result_promise_val;
}
let obj = iterable.as_object();
let length_atom = intern_str(ctx, "length");
let len_val = obj.get(length_atom).unwrap_or(JSValue::undefined());
if !len_val.is_int() {
let rp = result_promise_val.as_object_mut();
let err_val =
JSValue::new_string(ctx.intern("TypeError: Promise.allSettled requires an iterable"));
set_promise_result(ctx, rp, err_val);
set_promise_state(ctx, rp, PromiseState::Rejected);
return result_promise_val;
}
let len = len_val.get_int() as usize;
let pending_atom = intern_str(ctx, "__promise_all_pending__");
let result_promise_mut = result_promise_val.as_object_mut();
result_promise_mut.set(pending_atom, JSValue::new_int(len as i64));
for i in 0..len {
let idx_atom = ctx.intern(&i.to_string());
let item = obj.get(idx_atom).unwrap_or(JSValue::undefined());
let reaction = Reaction {
handler: JSValue::new_int(i as i64),
is_reject: false,
is_all_settled: true,
target_promise: JSValue::undefined(),
};
ctx.microtask_enqueue(Microtask::Reaction(reaction, item));
}
result_promise_val
}
fn promise_any(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
let iterable = args.get(0).copied().unwrap_or(JSValue::undefined());
let result_promise = create_promise(ctx);
let result_promise_ptr = Box::into_raw(Box::new(result_promise)) as usize;
let result_promise_val = JSValue::new_object(result_promise_ptr);
if !iterable.is_object() {
let rp = result_promise_val.as_object_mut();
let err_val =
JSValue::new_string(ctx.intern("TypeError: Promise.any requires an iterable"));
set_promise_result(ctx, rp, err_val);
set_promise_state(ctx, rp, PromiseState::Rejected);
return result_promise_val;
}
let obj = iterable.as_object();
let length_atom = intern_str(ctx, "length");
let len_val = obj.get(length_atom).unwrap_or(JSValue::undefined());
if !len_val.is_int() {
let rp = result_promise_val.as_object_mut();
let err_val =
JSValue::new_string(ctx.intern("TypeError: Promise.any requires an iterable"));
set_promise_result(ctx, rp, err_val);
set_promise_state(ctx, rp, PromiseState::Rejected);
return result_promise_val;
}
let len = len_val.get_int() as usize;
let pending_atom = intern_str(ctx, "__promise_any_pending__");
let result_promise_mut = result_promise_val.as_object_mut();
result_promise_mut.set(pending_atom, JSValue::new_int(len as i64));
for i in 0..len {
let idx_atom = ctx.intern(&i.to_string());
let item = obj.get(idx_atom).unwrap_or(JSValue::undefined());
let reaction = Reaction {
handler: JSValue::new_int(i as i64),
is_reject: false,
is_all_settled: false,
target_promise: JSValue::undefined(),
};
ctx.microtask_enqueue(Microtask::Reaction(reaction, item));
}
result_promise_val
}
fn promise_executor(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
if args.len() < 3 {
return JSValue::undefined();
}
let promise_val = args[0];
let resolve_fn = args[1];
let reject_fn = args[2];
if !promise_val.is_object() || !resolve_fn.is_function() || !reject_fn.is_function() {
return JSValue::undefined();
}
let promise_obj = promise_val.as_object_mut();
let result = get_promise_result(ctx, promise_obj);
if let Ok(result) = call_callback(ctx, resolve_fn, &[result]) {
if result.is_object() {
let result_obj = result.as_object_mut();
if result_obj.is_promise() {
let state = get_promise_state(ctx, result_obj);
let result_val = get_promise_result(ctx, result_obj);
let reactions_atom = intern_str(ctx, PROMISE_REACTIONS_SLOT);
match state {
PromiseState::Fulfilled => fulfill_promise(ctx, promise_obj, result_val),
PromiseState::Rejected => reject_promise(ctx, promise_obj, result_val),
PromiseState::Pending => {
let mut reactions = get_promise_reactions(ctx, result_obj);
reactions.push(Reaction {
handler: promise_val,
is_reject: false,
is_all_settled: false,
target_promise: JSValue::undefined(),
});
let reactions_arr = create_reaction_array(ctx, reactions);
result_obj.set(reactions_atom, reactions_arr);
}
}
} else {
fulfill_promise(ctx, promise_obj, result);
}
} else {
fulfill_promise(ctx, promise_obj, result);
}
}
JSValue::undefined()
}
fn call_callback(
ctx: &mut JSContext,
callback: JSValue,
args: &[JSValue],
) -> Result<JSValue, String> {
if let Some(ptr) = ctx.get_register_vm_ptr() {
let vm = unsafe { &mut *(ptr as *mut crate::runtime::vm::VM) };
vm.call_function(ctx, callback, args)
} else {
Err("VM not available".to_string())
}
}
fn create_promise_prototype(ctx: &mut JSContext) -> JSValue {
let mut proto = JSObject::new_promise();
let constructor_atom = intern_str(ctx, "constructor");
let then_atom = intern_str(ctx, "then");
let catch_atom = intern_str(ctx, "catch");
let finally_atom = intern_str(ctx, "finally");
proto.set(constructor_atom, JSValue::null());
proto.set(then_atom, create_builtin_function(ctx, "promise_then"));
proto.set(catch_atom, create_builtin_function(ctx, "promise_catch"));
proto.set(
finally_atom,
create_builtin_function(ctx, "promise_finally"),
);
if let Some(obj_proto_ptr) = ctx.get_object_prototype() {
proto.prototype = Some(obj_proto_ptr);
}
let ptr = Box::into_raw(Box::new(proto)) as usize;
ctx.runtime_mut().gc_heap_mut().track(ptr);
JSValue::new_object(ptr)
}
pub fn init_promise(ctx: &mut JSContext) {
let promise_atom = ctx.intern("Promise");
let mut promise_obj = JSObject::new_function();
let proto_value = create_promise_prototype(ctx);
let proto_ptr = proto_value.get_ptr();
ctx.set_promise_prototype(proto_ptr);
promise_obj.set(ctx.intern("prototype"), proto_value);
promise_obj.set(
ctx.intern("resolve"),
create_builtin_function(ctx, "promise_resolve"),
);
promise_obj.set(
ctx.intern("reject"),
create_builtin_function(ctx, "promise_reject"),
);
promise_obj.set(
ctx.intern("all"),
create_builtin_function(ctx, "promise_all"),
);
promise_obj.set(
ctx.intern("race"),
create_builtin_function(ctx, "promise_race"),
);
promise_obj.set(
ctx.intern("allSettled"),
create_builtin_function(ctx, "promise_allSettled"),
);
promise_obj.set(
ctx.intern("any"),
create_builtin_function(ctx, "promise_any"),
);
let promise_ptr = Box::into_raw(Box::new(promise_obj)) as usize;
let promise_value = JSValue::new_object(promise_ptr);
let global = ctx.global();
if global.is_object() {
let global_obj = global.as_object_mut();
global_obj.set(promise_atom, promise_value);
}
}
pub fn create_resolved_promise(ctx: &mut JSContext, value: JSValue) -> JSValue {
let mut promise = create_promise(ctx);
fulfill_promise(ctx, &mut promise, value);
let ptr = Box::into_raw(Box::new(promise)) as usize;
let result = JSValue::new_object(ptr);
result
}
pub fn create_rejected_promise(ctx: &mut JSContext, reason: JSValue) -> JSValue {
let mut promise = create_promise(ctx);
reject_promise(ctx, &mut promise, reason);
let ptr = Box::into_raw(Box::new(promise)) as usize;
JSValue::new_object(ptr)
}
#[cfg(feature = "process")]
pub(crate) fn fulfill_promise_with_value(
ctx: &mut JSContext,
promise_obj_ptr: usize,
value: JSValue,
) {
let promise = unsafe { &mut *(promise_obj_ptr as *mut JSObject) };
fulfill_promise(ctx, promise, value);
}
#[cfg(feature = "process")]
pub(crate) fn reject_promise_with_value(
ctx: &mut JSContext,
promise_obj_ptr: usize,
reason: JSValue,
) {
let promise = unsafe { &mut *(promise_obj_ptr as *mut JSObject) };
reject_promise(ctx, promise, reason);
}
pub fn run_microtasks_with_vm(ctx: &mut JSContext, vm: &mut crate::runtime::vm::VM) {
while let Some(task) = ctx.microtask_dequeue() {
match task {
Microtask::Reaction(reaction, argument) => {
let handler = reaction.handler;
if handler.is_function() {
let result = vm.call_function(ctx, handler, &[argument]);
if reaction.target_promise.is_object() {
let target = reaction.target_promise.as_object_mut();
if target.is_promise() {
match result {
Ok(val) => fulfill_promise(ctx, target, val),
Err(_) => {
reject_promise(ctx, target, JSValue::undefined());
}
}
}
}
}
}
Microtask::UserCallback(callback, args) => {
if callback.is_function() {
let _result = vm.call_function(ctx, callback, &args);
}
}
}
}
}
pub fn register_builtins(ctx: &mut JSContext) {
ctx.register_builtin(
"promise_executor",
HostFunction::new("executor", 3, promise_executor),
);
ctx.register_builtin(
"promise_resolve",
HostFunction::new("resolve", 1, promise_resolve),
);
ctx.register_builtin(
"promise_reject",
HostFunction::new("reject", 1, promise_reject),
);
ctx.register_builtin("promise_then", HostFunction::new("then", 2, promise_then));
ctx.register_builtin(
"promise_catch",
HostFunction::new("catch", 2, promise_catch),
);
ctx.register_builtin(
"promise_finally",
HostFunction::new("finally", 2, promise_finally),
);
ctx.register_builtin("promise_all", HostFunction::new("all", 1, promise_all));
ctx.register_builtin("promise_race", HostFunction::new("race", 1, promise_race));
ctx.register_builtin(
"promise_allSettled",
HostFunction::new("allSettled", 1, promise_all_settled),
);
ctx.register_builtin("promise_any", HostFunction::new("any", 1, promise_any));
}