Skip to main content

pipa/builtins/
promise.rs

1use crate::host::HostFunction;
2use crate::object::function::JSFunction;
3use crate::object::object::JSObject;
4use crate::runtime::context::JSContext;
5use crate::value::JSValue;
6use std::collections::VecDeque;
7
8const PROMISE_STATE_SLOT: &str = "__promise_state__";
9const PROMISE_RESULT_SLOT: &str = "__promise_result__";
10const PROMISE_REACTIONS_SLOT: &str = "__promise_reactions__";
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum PromiseState {
14    Pending,
15    Fulfilled,
16    Rejected,
17}
18
19#[derive(Clone)]
20pub struct Reaction {
21    pub handler: JSValue,
22    pub is_reject: bool,
23    pub is_all_settled: bool,
24
25    pub target_promise: JSValue,
26}
27
28pub struct MicrotaskQueue {
29    queue: VecDeque<Microtask>,
30}
31
32pub enum Microtask {
33    Reaction(Reaction, JSValue),
34
35    UserCallback(JSValue, Vec<JSValue>),
36}
37
38impl MicrotaskQueue {
39    pub fn new() -> Self {
40        MicrotaskQueue {
41            queue: VecDeque::new(),
42        }
43    }
44
45    pub fn enqueue(&mut self, task: Microtask) {
46        self.queue.push_back(task);
47    }
48
49    pub fn is_empty(&self) -> bool {
50        self.queue.is_empty()
51    }
52
53    pub fn dequeue(&mut self) -> Option<Microtask> {
54        self.queue.pop_front()
55    }
56}
57
58impl Default for MicrotaskQueue {
59    fn default() -> Self {
60        Self::new()
61    }
62}
63
64fn create_builtin_function(ctx: &mut JSContext, name: &str) -> JSValue {
65    let mut func = JSFunction::new_builtin(ctx.intern(name), 1);
66    func.set_builtin_marker(ctx, name);
67    let ptr = Box::into_raw(Box::new(func)) as usize;
68    ctx.runtime_mut().gc_heap_mut().track_function(ptr);
69    JSValue::new_function(ptr)
70}
71
72fn intern_str(ctx: &mut JSContext, s: &str) -> crate::runtime::atom::Atom {
73    ctx.intern(s)
74}
75
76fn get_promise_state(ctx: &mut JSContext, obj: &JSObject) -> PromiseState {
77    let state_atom = intern_str(ctx, PROMISE_STATE_SLOT);
78    if let Some(state_val) = obj.get(state_atom) {
79        let val = state_val.get_int();
80        match val {
81            1 => PromiseState::Fulfilled,
82            2 => PromiseState::Rejected,
83            _ => PromiseState::Pending,
84        }
85    } else {
86        PromiseState::Pending
87    }
88}
89
90fn set_promise_state(ctx: &mut JSContext, obj: &mut JSObject, state: PromiseState) {
91    let state_val = match state {
92        PromiseState::Pending => JSValue::new_int(0),
93        PromiseState::Fulfilled => JSValue::new_int(1),
94        PromiseState::Rejected => JSValue::new_int(2),
95    };
96    let state_atom = intern_str(ctx, PROMISE_STATE_SLOT);
97    obj.set(state_atom, state_val);
98}
99
100fn get_promise_result(ctx: &mut JSContext, obj: &JSObject) -> JSValue {
101    let result_atom = intern_str(ctx, PROMISE_RESULT_SLOT);
102    obj.get(result_atom).unwrap_or(JSValue::undefined())
103}
104
105fn set_promise_result(ctx: &mut JSContext, obj: &mut JSObject, value: JSValue) {
106    let result_atom = intern_str(ctx, PROMISE_RESULT_SLOT);
107    obj.set(result_atom, value);
108}
109
110fn get_promise_reactions(ctx: &mut JSContext, obj: &JSObject) -> Vec<Reaction> {
111    let reactions_atom = intern_str(ctx, PROMISE_REACTIONS_SLOT);
112    if let Some(reactions_val) = obj.get(reactions_atom) {
113        if reactions_val.is_object() {
114            let ptr = reactions_val.get_ptr();
115
116            if ptr == 0 {
117                return Vec::new();
118            }
119            let arr = reactions_val.as_object();
120            let len_atom = intern_str(ctx, "length");
121            let len = arr.get(len_atom).map(|v| v.get_int() as usize).unwrap_or(0);
122            let mut reactions = Vec::new();
123            for i in 0..len {
124                let idx_atom = intern_str(ctx, &i.to_string());
125                if let Some(r_val) = arr.get(idx_atom) {
126                    if r_val.is_object() {
127                        let r_obj = r_val.as_object();
128                        let handler_atom = intern_str(ctx, "handler");
129                        let is_reject_atom = intern_str(ctx, "isReject");
130                        if let Some(handler) = r_obj.get(handler_atom) {
131                            let is_reject = r_obj
132                                .get(is_reject_atom)
133                                .map(|v| v.get_bool())
134                                .unwrap_or(false);
135                            reactions.push(Reaction {
136                                handler,
137                                is_reject,
138                                is_all_settled: false,
139                                target_promise: JSValue::undefined(),
140                            });
141                        }
142                    }
143                }
144            }
145            return reactions;
146        }
147    }
148    Vec::new()
149}
150
151fn create_reaction_array(ctx: &mut JSContext, reactions: Vec<Reaction>) -> JSValue {
152    let mut arr = JSObject::new_array();
153    let len_atom = intern_str(ctx, "length");
154    arr.set(len_atom, JSValue::new_int(reactions.len() as i64));
155    for (i, reaction) in reactions.into_iter().enumerate() {
156        let mut r_obj = JSObject::new();
157        let handler_atom = intern_str(ctx, "handler");
158        let is_reject_atom = intern_str(ctx, "isReject");
159        r_obj.set(handler_atom, reaction.handler);
160        r_obj.set(is_reject_atom, JSValue::bool(reaction.is_reject));
161        let r_ptr = Box::into_raw(Box::new(r_obj)) as usize;
162        let idx_atom = intern_str(ctx, &i.to_string());
163        arr.set(idx_atom, JSValue::new_object(r_ptr));
164    }
165    let ptr = Box::into_raw(Box::new(arr)) as usize;
166    JSValue::new_object(ptr)
167}
168
169pub fn is_promise(value: &JSValue) -> bool {
170    if !value.is_object() {
171        return false;
172    }
173    let obj = value.as_object();
174    obj.is_promise()
175}
176
177fn create_promise(ctx: &mut JSContext) -> JSObject {
178    let mut promise = JSObject::new_promise();
179
180    if let Some(proto_ptr) = ctx.get_promise_prototype() {
181        promise.prototype = Some(proto_ptr);
182    }
183    let state_atom = intern_str(ctx, PROMISE_STATE_SLOT);
184    let result_atom = intern_str(ctx, PROMISE_RESULT_SLOT);
185    let reactions_atom = intern_str(ctx, PROMISE_REACTIONS_SLOT);
186    promise.set(state_atom, JSValue::new_int(0));
187    promise.set(result_atom, JSValue::undefined());
188    promise.set(reactions_atom, JSValue::null());
189    promise
190}
191
192fn fulfill_promise(ctx: &mut JSContext, promise: &mut JSObject, value: JSValue) {
193    set_promise_state(ctx, promise, PromiseState::Fulfilled);
194    set_promise_result(ctx, promise, value);
195    process_reactions(ctx, promise, PromiseState::Fulfilled);
196}
197
198fn reject_promise(ctx: &mut JSContext, promise: &mut JSObject, reason: JSValue) {
199    set_promise_state(ctx, promise, PromiseState::Rejected);
200    set_promise_result(ctx, promise, reason);
201    process_reactions(ctx, promise, PromiseState::Rejected);
202}
203
204fn process_reactions(ctx: &mut JSContext, promise: &mut JSObject, state: PromiseState) {
205    let reactions = get_promise_reactions(ctx, promise);
206    for reaction in reactions {
207        let argument = get_promise_result(ctx, promise);
208        let microtask = if reaction.is_reject && state == PromiseState::Rejected {
209            Microtask::Reaction(reaction, argument)
210        } else if !reaction.is_reject && state == PromiseState::Fulfilled {
211            Microtask::Reaction(reaction, argument)
212        } else {
213            continue;
214        };
215        ctx.microtask_enqueue(microtask);
216    }
217}
218
219fn promise_resolve(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
220    let value = if args.len() >= 2 {
221        args[1]
222    } else if !args.is_empty() {
223        args[0]
224    } else {
225        JSValue::undefined()
226    };
227    if value.is_object() {
228        let obj = value.as_object();
229        if obj.is_promise() {
230            return value;
231        }
232    }
233    let mut promise = create_promise(ctx);
234    fulfill_promise(ctx, &mut promise, value);
235    let ptr = Box::into_raw(Box::new(promise)) as usize;
236    JSValue::new_object(ptr)
237}
238
239fn promise_reject(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
240    let reason = if args.len() >= 2 {
241        args[1]
242    } else if !args.is_empty() {
243        args[0]
244    } else {
245        JSValue::undefined()
246    };
247    let mut promise = create_promise(ctx);
248    reject_promise(ctx, &mut promise, reason);
249    let ptr = Box::into_raw(Box::new(promise)) as usize;
250    JSValue::new_object(ptr)
251}
252
253fn promise_then(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
254    if args.is_empty() {
255        return JSValue::undefined();
256    }
257    let this_val = &args[0];
258    if !this_val.is_object() {
259        return JSValue::undefined();
260    }
261    let promise = this_val.as_object_mut();
262    if !promise.is_promise() {
263        return JSValue::undefined();
264    }
265    let on_fulfilled = args.get(1).copied().unwrap_or(JSValue::undefined());
266    let on_rejected = args.get(2).copied().unwrap_or(JSValue::undefined());
267    let new_promise = create_promise(ctx);
268    let ptr_new = Box::into_raw(Box::new(new_promise)) as usize;
269    let target = JSValue::new_object(ptr_new);
270    let state = get_promise_state(ctx, promise);
271    let reactions_atom = intern_str(ctx, PROMISE_REACTIONS_SLOT);
272    match state {
273        PromiseState::Fulfilled => {
274            let result = get_promise_result(ctx, promise);
275            let reaction = Reaction {
276                handler: on_fulfilled,
277                is_reject: false,
278                is_all_settled: false,
279                target_promise: target,
280            };
281            ctx.microtask_enqueue(Microtask::Reaction(reaction, result));
282        }
283        PromiseState::Rejected => {
284            let reason = get_promise_result(ctx, promise);
285            let reaction = Reaction {
286                handler: on_rejected,
287                is_reject: true,
288                is_all_settled: false,
289                target_promise: target,
290            };
291            ctx.microtask_enqueue(Microtask::Reaction(reaction, reason));
292        }
293        PromiseState::Pending => {
294            let mut reactions = get_promise_reactions(ctx, promise);
295            reactions.push(Reaction {
296                handler: on_fulfilled,
297                is_reject: false,
298                is_all_settled: false,
299                target_promise: target,
300            });
301            reactions.push(Reaction {
302                handler: on_rejected,
303                is_reject: true,
304                is_all_settled: false,
305                target_promise: target,
306            });
307            let reactions_arr = create_reaction_array(ctx, reactions);
308            promise.set(reactions_atom, reactions_arr);
309        }
310    }
311    target
312}
313
314fn promise_catch(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
315    if args.is_empty() {
316        return JSValue::undefined();
317    }
318    let this_val = &args[0];
319    let on_rejected = args.get(1).copied().unwrap_or(JSValue::undefined());
320    if !this_val.is_object() {
321        return JSValue::undefined();
322    }
323    let promise = this_val.as_object_mut();
324    if !promise.is_promise() {
325        return JSValue::undefined();
326    }
327    let new_promise = create_promise(ctx);
328    let ptr_new = Box::into_raw(Box::new(new_promise)) as usize;
329    let target = JSValue::new_object(ptr_new);
330    let state = get_promise_state(ctx, promise);
331    let reactions_atom = intern_str(ctx, PROMISE_REACTIONS_SLOT);
332    match state {
333        PromiseState::Fulfilled => {
334            let result = get_promise_result(ctx, promise);
335            let reaction = Reaction {
336                handler: on_rejected,
337                is_reject: false,
338                is_all_settled: false,
339                target_promise: target,
340            };
341            ctx.microtask_enqueue(Microtask::Reaction(reaction, result));
342        }
343        PromiseState::Rejected => {
344            let reason = get_promise_result(ctx, promise);
345            let reaction = Reaction {
346                handler: on_rejected,
347                is_reject: true,
348                is_all_settled: false,
349                target_promise: target,
350            };
351            ctx.microtask_enqueue(Microtask::Reaction(reaction, reason));
352        }
353        PromiseState::Pending => {
354            let mut reactions = get_promise_reactions(ctx, promise);
355            reactions.push(Reaction {
356                handler: on_rejected,
357                is_reject: true,
358                is_all_settled: false,
359                target_promise: target,
360            });
361            let reactions_arr = create_reaction_array(ctx, reactions);
362            promise.set(reactions_atom, reactions_arr);
363        }
364    }
365    target
366}
367
368fn promise_finally(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
369    if args.is_empty() {
370        return JSValue::undefined();
371    }
372    let this_val = &args[0];
373    let on_finally = args.get(1).copied().unwrap_or(JSValue::undefined());
374    if !this_val.is_object() {
375        return JSValue::undefined();
376    }
377    let promise = this_val.as_object_mut();
378    if !promise.is_promise() {
379        return JSValue::undefined();
380    }
381    let new_promise = create_promise(ctx);
382    let ptr_new = Box::into_raw(Box::new(new_promise)) as usize;
383    let target = JSValue::new_object(ptr_new);
384    let state = get_promise_state(ctx, promise);
385    let reactions_atom = intern_str(ctx, PROMISE_REACTIONS_SLOT);
386    match state {
387        PromiseState::Fulfilled => {
388            let result = get_promise_result(ctx, promise);
389            let reaction = Reaction {
390                handler: on_finally,
391                is_reject: false,
392                is_all_settled: false,
393                target_promise: target,
394            };
395            ctx.microtask_enqueue(Microtask::Reaction(reaction, result));
396        }
397        PromiseState::Rejected => {
398            let reason = get_promise_result(ctx, promise);
399            let reaction = Reaction {
400                handler: on_finally,
401                is_reject: true,
402                is_all_settled: false,
403                target_promise: target,
404            };
405            ctx.microtask_enqueue(Microtask::Reaction(reaction, reason));
406        }
407        PromiseState::Pending => {
408            let mut reactions = get_promise_reactions(ctx, promise);
409            reactions.push(Reaction {
410                handler: on_finally,
411                is_reject: false,
412                is_all_settled: false,
413                target_promise: target,
414            });
415            reactions.push(Reaction {
416                handler: on_finally,
417                is_reject: true,
418                is_all_settled: false,
419                target_promise: target,
420            });
421            let reactions_arr = create_reaction_array(ctx, reactions);
422            promise.set(reactions_atom, reactions_arr);
423        }
424    }
425    target
426}
427
428fn promise_all(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
429    let iterable = args.get(0).copied().unwrap_or(JSValue::undefined());
430
431    let result_promise = create_promise(ctx);
432    let result_promise_ptr = Box::into_raw(Box::new(result_promise)) as usize;
433    let result_promise_val = JSValue::new_object(result_promise_ptr);
434
435    if !iterable.is_object() {
436        let rp = result_promise_val.as_object_mut();
437        let err_val =
438            JSValue::new_string(ctx.intern("TypeError: Promise.all requires an iterable"));
439        set_promise_result(ctx, rp, err_val);
440        set_promise_state(ctx, rp, PromiseState::Rejected);
441        return result_promise_val;
442    }
443
444    let obj = iterable.as_object();
445
446    let length_atom = intern_str(ctx, "length");
447    let len_val = obj.get(length_atom).unwrap_or(JSValue::undefined());
448    if !len_val.is_int() {
449        let rp = result_promise_val.as_object_mut();
450        let err_val =
451            JSValue::new_string(ctx.intern("TypeError: Promise.all requires an iterable"));
452        set_promise_result(ctx, rp, err_val);
453        set_promise_state(ctx, rp, PromiseState::Rejected);
454        return result_promise_val;
455    }
456
457    let len = len_val.get_int() as usize;
458
459    let pending_atom = ctx.intern("__promise_all_pending__");
460    let result_promise_mut = result_promise_val.as_object_mut();
461    result_promise_mut.set(pending_atom, JSValue::new_int(len as i64));
462
463    for i in 0..len {
464        let idx_atom = ctx.intern(&i.to_string());
465        let item = obj.get(idx_atom).unwrap_or(JSValue::undefined());
466
467        let reaction = Reaction {
468            handler: JSValue::new_int(i as i64),
469            is_reject: false,
470            is_all_settled: false,
471            target_promise: JSValue::undefined(),
472        };
473        ctx.microtask_enqueue(Microtask::Reaction(reaction, item));
474    }
475
476    result_promise_val
477}
478
479fn promise_race(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
480    let iterable = args.get(0).copied().unwrap_or(JSValue::undefined());
481
482    let result_promise = create_promise(ctx);
483    let result_promise_ptr = Box::into_raw(Box::new(result_promise)) as usize;
484    let result_promise_val = JSValue::new_object(result_promise_ptr);
485
486    if !iterable.is_object() {
487        let rp = result_promise_val.as_object_mut();
488        let err_val =
489            JSValue::new_string(ctx.intern("TypeError: Promise.race requires an iterable"));
490        set_promise_result(ctx, rp, err_val);
491        set_promise_state(ctx, rp, PromiseState::Rejected);
492        return result_promise_val;
493    }
494
495    let obj = iterable.as_object();
496
497    let length_atom = intern_str(ctx, "length");
498    let len_val = obj.get(length_atom).unwrap_or(JSValue::undefined());
499    if !len_val.is_int() {
500        let rp = result_promise_val.as_object_mut();
501        let err_val =
502            JSValue::new_string(ctx.intern("TypeError: Promise.race requires an iterable"));
503        set_promise_result(ctx, rp, err_val);
504        set_promise_state(ctx, rp, PromiseState::Rejected);
505        return result_promise_val;
506    }
507
508    let len = len_val.get_int() as usize;
509
510    if len == 0 {
511        return result_promise_val;
512    }
513
514    for i in 0..len {
515        let idx_atom = ctx.intern(&i.to_string());
516        let item = obj.get(idx_atom).unwrap_or(JSValue::undefined());
517
518        let reaction = Reaction {
519            handler: JSValue::new_int(i as i64),
520            is_reject: false,
521            is_all_settled: false,
522            target_promise: JSValue::undefined(),
523        };
524        ctx.microtask_enqueue(Microtask::Reaction(reaction, item));
525    }
526
527    result_promise_val
528}
529
530fn promise_all_settled(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
531    let iterable = args.get(0).copied().unwrap_or(JSValue::undefined());
532
533    let result_promise = create_promise(ctx);
534    let result_promise_ptr = Box::into_raw(Box::new(result_promise)) as usize;
535    let result_promise_val = JSValue::new_object(result_promise_ptr);
536
537    if !iterable.is_object() {
538        let rp = result_promise_val.as_object_mut();
539        let err_val =
540            JSValue::new_string(ctx.intern("TypeError: Promise.allSettled requires an iterable"));
541        set_promise_result(ctx, rp, err_val);
542        set_promise_state(ctx, rp, PromiseState::Rejected);
543        return result_promise_val;
544    }
545
546    let obj = iterable.as_object();
547
548    let length_atom = intern_str(ctx, "length");
549    let len_val = obj.get(length_atom).unwrap_or(JSValue::undefined());
550    if !len_val.is_int() {
551        let rp = result_promise_val.as_object_mut();
552        let err_val =
553            JSValue::new_string(ctx.intern("TypeError: Promise.allSettled requires an iterable"));
554        set_promise_result(ctx, rp, err_val);
555        set_promise_state(ctx, rp, PromiseState::Rejected);
556        return result_promise_val;
557    }
558
559    let len = len_val.get_int() as usize;
560
561    let pending_atom = intern_str(ctx, "__promise_all_pending__");
562    let result_promise_mut = result_promise_val.as_object_mut();
563    result_promise_mut.set(pending_atom, JSValue::new_int(len as i64));
564
565    for i in 0..len {
566        let idx_atom = ctx.intern(&i.to_string());
567        let item = obj.get(idx_atom).unwrap_or(JSValue::undefined());
568
569        let reaction = Reaction {
570            handler: JSValue::new_int(i as i64),
571            is_reject: false,
572            is_all_settled: true,
573            target_promise: JSValue::undefined(),
574        };
575        ctx.microtask_enqueue(Microtask::Reaction(reaction, item));
576    }
577
578    result_promise_val
579}
580
581fn promise_any(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
582    let iterable = args.get(0).copied().unwrap_or(JSValue::undefined());
583
584    let result_promise = create_promise(ctx);
585    let result_promise_ptr = Box::into_raw(Box::new(result_promise)) as usize;
586    let result_promise_val = JSValue::new_object(result_promise_ptr);
587
588    if !iterable.is_object() {
589        let rp = result_promise_val.as_object_mut();
590        let err_val =
591            JSValue::new_string(ctx.intern("TypeError: Promise.any requires an iterable"));
592        set_promise_result(ctx, rp, err_val);
593        set_promise_state(ctx, rp, PromiseState::Rejected);
594        return result_promise_val;
595    }
596
597    let obj = iterable.as_object();
598
599    let length_atom = intern_str(ctx, "length");
600    let len_val = obj.get(length_atom).unwrap_or(JSValue::undefined());
601    if !len_val.is_int() {
602        let rp = result_promise_val.as_object_mut();
603        let err_val =
604            JSValue::new_string(ctx.intern("TypeError: Promise.any requires an iterable"));
605        set_promise_result(ctx, rp, err_val);
606        set_promise_state(ctx, rp, PromiseState::Rejected);
607        return result_promise_val;
608    }
609
610    let len = len_val.get_int() as usize;
611
612    let pending_atom = intern_str(ctx, "__promise_any_pending__");
613    let result_promise_mut = result_promise_val.as_object_mut();
614    result_promise_mut.set(pending_atom, JSValue::new_int(len as i64));
615
616    for i in 0..len {
617        let idx_atom = ctx.intern(&i.to_string());
618        let item = obj.get(idx_atom).unwrap_or(JSValue::undefined());
619
620        let reaction = Reaction {
621            handler: JSValue::new_int(i as i64),
622            is_reject: false,
623            is_all_settled: false,
624            target_promise: JSValue::undefined(),
625        };
626        ctx.microtask_enqueue(Microtask::Reaction(reaction, item));
627    }
628
629    result_promise_val
630}
631
632fn promise_executor(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
633    if args.len() < 3 {
634        return JSValue::undefined();
635    }
636    let promise_val = args[0];
637    let resolve_fn = args[1];
638    let reject_fn = args[2];
639    if !promise_val.is_object() || !resolve_fn.is_function() || !reject_fn.is_function() {
640        return JSValue::undefined();
641    }
642    let promise_obj = promise_val.as_object_mut();
643    let result = get_promise_result(ctx, promise_obj);
644    if let Ok(result) = call_callback(ctx, resolve_fn, &[result]) {
645        if result.is_object() {
646            let result_obj = result.as_object_mut();
647            if result_obj.is_promise() {
648                let state = get_promise_state(ctx, result_obj);
649                let result_val = get_promise_result(ctx, result_obj);
650                let reactions_atom = intern_str(ctx, PROMISE_REACTIONS_SLOT);
651                match state {
652                    PromiseState::Fulfilled => fulfill_promise(ctx, promise_obj, result_val),
653                    PromiseState::Rejected => reject_promise(ctx, promise_obj, result_val),
654                    PromiseState::Pending => {
655                        let mut reactions = get_promise_reactions(ctx, result_obj);
656                        reactions.push(Reaction {
657                            handler: promise_val,
658                            is_reject: false,
659                            is_all_settled: false,
660                            target_promise: JSValue::undefined(),
661                        });
662                        let reactions_arr = create_reaction_array(ctx, reactions);
663                        result_obj.set(reactions_atom, reactions_arr);
664                    }
665                }
666            } else {
667                fulfill_promise(ctx, promise_obj, result);
668            }
669        } else {
670            fulfill_promise(ctx, promise_obj, result);
671        }
672    }
673    JSValue::undefined()
674}
675
676fn call_callback(
677    ctx: &mut JSContext,
678    callback: JSValue,
679    args: &[JSValue],
680) -> Result<JSValue, String> {
681    if let Some(ptr) = ctx.get_register_vm_ptr() {
682        let vm = unsafe { &mut *(ptr as *mut crate::runtime::vm::VM) };
683        vm.call_function(ctx, callback, args)
684    } else {
685        Err("VM not available".to_string())
686    }
687}
688
689fn create_promise_prototype(ctx: &mut JSContext) -> JSValue {
690    let mut proto = JSObject::new_promise();
691    let constructor_atom = intern_str(ctx, "constructor");
692    let then_atom = intern_str(ctx, "then");
693    let catch_atom = intern_str(ctx, "catch");
694    let finally_atom = intern_str(ctx, "finally");
695    proto.set(constructor_atom, JSValue::null());
696    proto.set(then_atom, create_builtin_function(ctx, "promise_then"));
697    proto.set(catch_atom, create_builtin_function(ctx, "promise_catch"));
698    proto.set(
699        finally_atom,
700        create_builtin_function(ctx, "promise_finally"),
701    );
702
703    if let Some(obj_proto_ptr) = ctx.get_object_prototype() {
704        proto.prototype = Some(obj_proto_ptr);
705    }
706    let ptr = Box::into_raw(Box::new(proto)) as usize;
707    ctx.runtime_mut().gc_heap_mut().track(ptr);
708    JSValue::new_object(ptr)
709}
710
711pub fn init_promise(ctx: &mut JSContext) {
712    let promise_atom = ctx.intern("Promise");
713    let mut promise_obj = JSObject::new_function();
714    let proto_value = create_promise_prototype(ctx);
715
716    let proto_ptr = proto_value.get_ptr();
717    ctx.set_promise_prototype(proto_ptr);
718    promise_obj.set(ctx.intern("prototype"), proto_value);
719    promise_obj.set(
720        ctx.intern("resolve"),
721        create_builtin_function(ctx, "promise_resolve"),
722    );
723    promise_obj.set(
724        ctx.intern("reject"),
725        create_builtin_function(ctx, "promise_reject"),
726    );
727    promise_obj.set(
728        ctx.intern("all"),
729        create_builtin_function(ctx, "promise_all"),
730    );
731    promise_obj.set(
732        ctx.intern("race"),
733        create_builtin_function(ctx, "promise_race"),
734    );
735    promise_obj.set(
736        ctx.intern("allSettled"),
737        create_builtin_function(ctx, "promise_allSettled"),
738    );
739    promise_obj.set(
740        ctx.intern("any"),
741        create_builtin_function(ctx, "promise_any"),
742    );
743    let promise_ptr = Box::into_raw(Box::new(promise_obj)) as usize;
744    let promise_value = JSValue::new_object(promise_ptr);
745    let global = ctx.global();
746    if global.is_object() {
747        let global_obj = global.as_object_mut();
748        global_obj.set(promise_atom, promise_value);
749    }
750}
751
752pub fn create_resolved_promise(ctx: &mut JSContext, value: JSValue) -> JSValue {
753    let mut promise = create_promise(ctx);
754    fulfill_promise(ctx, &mut promise, value);
755    let ptr = Box::into_raw(Box::new(promise)) as usize;
756    let result = JSValue::new_object(ptr);
757    result
758}
759
760pub fn create_rejected_promise(ctx: &mut JSContext, reason: JSValue) -> JSValue {
761    let mut promise = create_promise(ctx);
762    reject_promise(ctx, &mut promise, reason);
763    let ptr = Box::into_raw(Box::new(promise)) as usize;
764    JSValue::new_object(ptr)
765}
766
767#[cfg(any(feature = "process", feature = "fetch"))]
768pub(crate) fn fulfill_promise_with_value(
769    ctx: &mut JSContext,
770    promise_obj_ptr: usize,
771    value: JSValue,
772) {
773    let promise = unsafe { &mut *(promise_obj_ptr as *mut JSObject) };
774    fulfill_promise(ctx, promise, value);
775}
776
777#[cfg(any(feature = "process", feature = "fetch"))]
778pub(crate) fn reject_promise_with_value(
779    ctx: &mut JSContext,
780    promise_obj_ptr: usize,
781    reason: JSValue,
782) {
783    let promise = unsafe { &mut *(promise_obj_ptr as *mut JSObject) };
784    reject_promise(ctx, promise, reason);
785}
786
787#[cfg(feature = "fetch")]
788pub fn create_pending_promise(ctx: &mut JSContext) -> JSValue {
789    let promise = create_promise(ctx);
790    let ptr = Box::into_raw(Box::new(promise)) as usize;
791    JSValue::new_object(ptr)
792}
793
794pub fn run_microtasks_with_vm(ctx: &mut JSContext, vm: &mut crate::runtime::vm::VM) {
795    while let Some(task) = ctx.microtask_dequeue() {
796        match task {
797            Microtask::Reaction(reaction, argument) => {
798                let handler = reaction.handler;
799                if handler.is_function() {
800                    let result = vm.call_function(ctx, handler, &[argument]);
801                    if reaction.target_promise.is_object() {
802                        let target = reaction.target_promise.as_object_mut();
803                        if target.is_promise() {
804                            match result {
805                                Ok(val) => fulfill_promise(ctx, target, val),
806                                Err(_) => {
807                                    reject_promise(ctx, target, JSValue::undefined());
808                                }
809                            }
810                        }
811                    }
812                }
813            }
814            Microtask::UserCallback(callback, args) => {
815                if callback.is_function() {
816                    let _result = vm.call_function(ctx, callback, &args);
817                }
818            }
819        }
820    }
821}
822
823pub fn register_builtins(ctx: &mut JSContext) {
824    ctx.register_builtin(
825        "promise_executor",
826        HostFunction::new("executor", 3, promise_executor),
827    );
828    ctx.register_builtin(
829        "promise_resolve",
830        HostFunction::new("resolve", 1, promise_resolve),
831    );
832    ctx.register_builtin(
833        "promise_reject",
834        HostFunction::new("reject", 1, promise_reject),
835    );
836    ctx.register_builtin("promise_then", HostFunction::new("then", 2, promise_then));
837    ctx.register_builtin(
838        "promise_catch",
839        HostFunction::new("catch", 2, promise_catch),
840    );
841    ctx.register_builtin(
842        "promise_finally",
843        HostFunction::new("finally", 2, promise_finally),
844    );
845    ctx.register_builtin("promise_all", HostFunction::new("all", 1, promise_all));
846    ctx.register_builtin("promise_race", HostFunction::new("race", 1, promise_race));
847    ctx.register_builtin(
848        "promise_allSettled",
849        HostFunction::new("allSettled", 1, promise_all_settled),
850    );
851    ctx.register_builtin("promise_any", HostFunction::new("any", 1, promise_any));
852}