boa_engine/builtins/promise/
mod.rs

1//! Boa's implementation of ECMAScript's global `Promise` object.
2
3#[cfg(test)]
4mod tests;
5
6use super::{
7    BuiltInBuilder, BuiltInConstructor, IntrinsicObject,
8    iterable::{IteratorHint, IteratorRecord},
9};
10use crate::{
11    Context, JsArgs, JsError, JsResult, JsString,
12    builtins::{Array, BuiltInObject},
13    context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
14    error::JsNativeError,
15    job::{JobCallback, PromiseJob},
16    js_string,
17    native_function::NativeFunction,
18    object::{
19        CONSTRUCTOR, FunctionObjectBuilder, JsFunction, JsObject,
20        internal_methods::get_prototype_from_constructor,
21    },
22    property::Attribute,
23    realm::Realm,
24    string::StaticJsStrings,
25    symbol::JsSymbol,
26    value::JsValue,
27};
28use boa_gc::{Finalize, Gc, GcRefCell, Trace, custom_trace};
29use boa_macros::JsData;
30use std::{cell::Cell, rc::Rc};
31use tap::{Conv, Pipe};
32
33// ==================== Public API ====================
34
35/// The current state of a [`Promise`].
36#[derive(Debug, Clone, Finalize, PartialEq, Eq)]
37pub enum PromiseState {
38    /// The promise hasn't been resolved.
39    Pending,
40    /// The promise was fulfilled with a success value.
41    Fulfilled(JsValue),
42    /// The promise was rejected with a failure reason.
43    Rejected(JsValue),
44}
45
46unsafe impl Trace for PromiseState {
47    custom_trace!(this, mark, {
48        match this {
49            Self::Fulfilled(v) | Self::Rejected(v) => mark(v),
50            Self::Pending => {}
51        }
52    });
53}
54
55impl PromiseState {
56    /// Gets the inner `JsValue` of a fulfilled promise state, or returns `None` if
57    /// the state is not `Fulfilled`.
58    #[must_use]
59    pub const fn as_fulfilled(&self) -> Option<&JsValue> {
60        match self {
61            Self::Fulfilled(v) => Some(v),
62            _ => None,
63        }
64    }
65
66    /// Gets the inner `JsValue` of a rejected promise state, or returns `None` if
67    /// the state is not `Rejected`.
68    #[must_use]
69    pub const fn as_rejected(&self) -> Option<&JsValue> {
70        match self {
71            Self::Rejected(v) => Some(v),
72            _ => None,
73        }
74    }
75}
76
77/// The internal representation of a `Promise` object.
78#[derive(Debug, Trace, Finalize, JsData)]
79pub struct Promise {
80    state: PromiseState,
81    fulfill_reactions: Vec<ReactionRecord>,
82    reject_reactions: Vec<ReactionRecord>,
83    handled: bool,
84}
85
86/// The operation type of the [`HostPromiseRejectionTracker`][fn] abstract operation.
87///
88/// # Note
89///
90/// Per the spec:
91///
92/// > If operation is "handle", an implementation should not hold a reference to promise in a way
93/// > that would interfere with garbage collection. An implementation may hold a reference to promise
94/// > if operation is "reject", since it is expected that rejections will be rare and not on hot code paths.
95///
96/// [fn]: https://tc39.es/ecma262/#sec-host-promise-rejection-tracker
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
98pub enum OperationType {
99    /// A promise was rejected without any handlers.
100    Reject,
101    /// A handler was added to a rejected promise for the first time.
102    Handle,
103}
104
105/// Functions used to resolve a pending promise.
106///
107/// This is equivalent to the parameters `resolveFunc` and `rejectFunc` of the executor passed to
108/// the [`Promise()`] constructor.
109///
110/// Both functions are always associated with the promise from which they were created. This
111/// means that by simply calling `resolve.call(this, &[values], context)` or
112/// `reject.call(this, &[error], context)`, the state of the original promise will be updated with
113/// the resolution value.
114///
115/// [`Promise()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise
116#[derive(Debug, Clone, Finalize)]
117pub struct ResolvingFunctions {
118    /// The `resolveFunc` parameter of the executor passed to `Promise()`.
119    pub resolve: JsFunction,
120    /// The `rejectFunc` parameter of the executor passed to `Promise()`.
121    pub reject: JsFunction,
122}
123
124// Manually implementing `Trace` to allow destructuring.
125unsafe impl Trace for ResolvingFunctions {
126    custom_trace!(this, mark, {
127        mark(&this.resolve);
128        mark(&this.reject);
129    });
130}
131
132// ==================== Private API ====================
133
134/// `IfAbruptRejectPromise ( value, capability )`
135///
136/// `IfAbruptRejectPromise` is a shorthand for a sequence of algorithm steps that use a `PromiseCapability` Record.
137///
138/// More information:
139///  - [ECMAScript reference][spec]
140///
141/// [spec]: https://tc39.es/ecma262/#sec-ifabruptrejectpromise
142macro_rules! if_abrupt_reject_promise {
143    ($value:expr, $capability:expr, $context: expr) => {
144        match $value {
145            // 1. If value is an abrupt completion, then
146            Err(err) => {
147                let err = err.to_opaque($context);
148                // a. Perform ? Call(capability.[[Reject]], undefined, « value.[[Value]] »).
149                $capability
150                    .reject()
151                    .call(&JsValue::undefined(), &[err], $context)?;
152
153                // b. Return capability.[[Promise]].
154                return Ok($capability.promise().clone().into());
155            }
156            // 2. Else if value is a Completion Record, set value to value.[[Value]].
157            Ok(value) => value,
158        }
159    };
160}
161
162pub(crate) use if_abrupt_reject_promise;
163
164/// The internal `PromiseCapability` data type.
165///
166/// More information:
167///  - [ECMAScript reference][spec]
168///
169/// [spec]: https://tc39.es/ecma262/#sec-promisecapability-records
170#[derive(Debug, Clone, Finalize)]
171pub(crate) struct PromiseCapability {
172    /// The `[[Promise]]` field.
173    pub(crate) promise: JsObject,
174
175    /// The resolving functions,
176    pub(crate) functions: ResolvingFunctions,
177}
178
179// SAFETY: manually implementing `Trace` to allow destructuring.
180unsafe impl Trace for PromiseCapability {
181    custom_trace!(this, mark, {
182        mark(&this.promise);
183        mark(&this.functions);
184    });
185}
186
187/// The internal `PromiseReaction` data type.
188///
189/// More information:
190///  - [ECMAScript reference][spec]
191///
192/// [spec]: https://tc39.es/ecma262/#sec-promisereaction-records
193#[derive(Debug, Trace, Finalize)]
194pub(crate) struct ReactionRecord {
195    /// The `[[Capability]]` field.
196    promise_capability: Option<PromiseCapability>,
197
198    /// The `[[Type]]` field.
199    #[unsafe_ignore_trace]
200    reaction_type: ReactionType,
201
202    /// The `[[Handler]]` field.
203    handler: Option<JobCallback>,
204}
205
206/// The `[[Type]]` field values of a `PromiseReaction` record.
207///
208/// More information:
209///  - [ECMAScript reference][spec]
210///
211/// [spec]: https://tc39.es/ecma262/#sec-promisereaction-records
212#[derive(Debug, Clone, Copy)]
213enum ReactionType {
214    Fulfill,
215    Reject,
216}
217
218impl PromiseCapability {
219    /// `NewPromiseCapability ( C )`
220    ///
221    /// More information:
222    ///  - [ECMAScript reference][spec]
223    ///
224    /// [spec]: https://tc39.es/ecma262/#sec-newpromisecapability
225    pub(crate) fn new(c: &JsObject, context: &mut Context) -> JsResult<Self> {
226        #[derive(Debug, Clone, Trace, Finalize)]
227        struct RejectResolve {
228            reject: JsValue,
229            resolve: JsValue,
230        }
231
232        // 1. If IsConstructor(C) is false, throw a TypeError exception.
233        if !c.is_constructor() {
234            return Err(JsNativeError::typ()
235                .with_message("PromiseCapability: expected constructor")
236                .into());
237        }
238
239        // 2. NOTE: C is assumed to be a constructor function that supports the parameter conventions of the Promise constructor (see 27.2.3.1).
240        // 3. Let promiseCapability be the PromiseCapability Record { [[Promise]]: undefined, [[Resolve]]: undefined, [[Reject]]: undefined }.
241        let promise_capability = Gc::new(GcRefCell::new(RejectResolve {
242            reject: JsValue::undefined(),
243            resolve: JsValue::undefined(),
244        }));
245
246        // 4. Let executorClosure be a new Abstract Closure with parameters (resolve, reject) that captures promiseCapability and performs the following steps when called:
247        // 5. Let executor be CreateBuiltinFunction(executorClosure, 2, "", « »).
248        let executor = FunctionObjectBuilder::new(
249            context.realm(),
250            NativeFunction::from_copy_closure_with_captures(
251                |_this, args: &[JsValue], captures, _| {
252                    let mut promise_capability = captures.borrow_mut();
253                    // a. If promiseCapability.[[Resolve]] is not undefined, throw a TypeError exception.
254                    if !promise_capability.resolve.is_undefined() {
255                        return Err(JsNativeError::typ()
256                            .with_message("promiseCapability.[[Resolve]] is not undefined")
257                            .into());
258                    }
259
260                    // b. If promiseCapability.[[Reject]] is not undefined, throw a TypeError exception.
261                    if !promise_capability.reject.is_undefined() {
262                        return Err(JsNativeError::typ()
263                            .with_message("promiseCapability.[[Reject]] is not undefined")
264                            .into());
265                    }
266
267                    let resolve = args.get_or_undefined(0);
268                    let reject = args.get_or_undefined(1);
269
270                    // c. Set promiseCapability.[[Resolve]] to resolve.
271                    promise_capability.resolve = resolve.clone();
272
273                    // d. Set promiseCapability.[[Reject]] to reject.
274                    promise_capability.reject = reject.clone();
275
276                    // e. Return undefined.
277                    Ok(JsValue::undefined())
278                },
279                promise_capability.clone(),
280            ),
281        )
282        .name("")
283        .length(2)
284        .build()
285        .into();
286
287        // 6. Let promise be ? Construct(C, « executor »).
288        let promise = c.construct(&[executor], None, context)?;
289
290        let promise_capability = promise_capability.borrow();
291
292        let resolve = promise_capability.resolve.clone();
293        let reject = promise_capability.reject.clone();
294
295        // 7. If IsCallable(promiseCapability.[[Resolve]]) is false, throw a TypeError exception.
296        let resolve = resolve
297            .as_object()
298            .and_then(JsFunction::from_object)
299            .ok_or_else(|| {
300                JsNativeError::typ().with_message("promiseCapability.[[Resolve]] is not callable")
301            })?;
302
303        // 8. If IsCallable(promiseCapability.[[Reject]]) is false, throw a TypeError exception.
304        let reject = reject
305            .as_object()
306            .and_then(JsFunction::from_object)
307            .ok_or_else(|| {
308                JsNativeError::typ().with_message("promiseCapability.[[Reject]] is not callable")
309            })?;
310
311        // 9. Set promiseCapability.[[Promise]] to promise.
312        // 10. Return promiseCapability.
313        Ok(Self {
314            promise,
315            functions: ResolvingFunctions { resolve, reject },
316        })
317    }
318
319    /// Returns the promise object.
320    pub(crate) const fn promise(&self) -> &JsObject {
321        &self.promise
322    }
323
324    /// Returns the resolve function.
325    pub(crate) const fn resolve(&self) -> &JsFunction {
326        &self.functions.resolve
327    }
328
329    /// Returns the reject function.
330    pub(crate) const fn reject(&self) -> &JsFunction {
331        &self.functions.reject
332    }
333}
334
335impl IntrinsicObject for Promise {
336    fn init(realm: &Realm) {
337        let get_species = BuiltInBuilder::callable(realm, Self::get_species)
338            .name(js_string!("get [Symbol.species]"))
339            .build();
340
341        BuiltInBuilder::from_standard_constructor::<Self>(realm)
342            .static_method(Self::all, js_string!("all"), 1)
343            .static_method(Self::all_settled, js_string!("allSettled"), 1)
344            .static_method(Self::any, js_string!("any"), 1)
345            .static_method(Self::race, js_string!("race"), 1)
346            .static_method(Self::reject, js_string!("reject"), 1)
347            .static_method(Self::resolve, js_string!("resolve"), 1)
348            .static_method(Self::r#try, js_string!("try"), 1)
349            .static_method(Self::with_resolvers, js_string!("withResolvers"), 0)
350            .static_accessor(
351                JsSymbol::species(),
352                Some(get_species),
353                None,
354                Attribute::CONFIGURABLE,
355            )
356            .method(Self::then, js_string!("then"), 2)
357            .method(Self::catch, js_string!("catch"), 1)
358            .method(Self::finally, js_string!("finally"), 1)
359            // <https://tc39.es/ecma262/#sec-promise.prototype-@@tostringtag>
360            .property(
361                JsSymbol::to_string_tag(),
362                Self::NAME,
363                Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
364            )
365            .build();
366    }
367
368    fn get(intrinsics: &Intrinsics) -> JsObject {
369        Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor()
370    }
371}
372
373impl BuiltInObject for Promise {
374    const NAME: JsString = StaticJsStrings::PROMISE;
375}
376
377impl BuiltInConstructor for Promise {
378    const CONSTRUCTOR_ARGUMENTS: usize = 1;
379    const PROTOTYPE_STORAGE_SLOTS: usize = 4;
380    const CONSTRUCTOR_STORAGE_SLOTS: usize = 10;
381
382    const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor =
383        StandardConstructors::promise;
384
385    /// `Promise ( executor )`
386    ///
387    /// More information:
388    ///  - [ECMAScript reference][spec]
389    ///
390    /// [spec]: https://tc39.es/ecma262/#sec-promise-executor
391    fn constructor(
392        new_target: &JsValue,
393        args: &[JsValue],
394        context: &mut Context,
395    ) -> JsResult<JsValue> {
396        // 1. If NewTarget is undefined, throw a TypeError exception.
397        if new_target.is_undefined() {
398            return Err(JsNativeError::typ()
399                .with_message("Promise NewTarget cannot be undefined")
400                .into());
401        }
402
403        // 2. If IsCallable(executor) is false, throw a TypeError exception.
404        let executor = args
405            .get_or_undefined(0)
406            .as_callable()
407            .ok_or_else(|| JsNativeError::typ().with_message("Promise executor is not callable"))?;
408
409        // 3. Let promise be ? OrdinaryCreateFromConstructor(NewTarget, "%Promise.prototype%", « [[PromiseState]], [[PromiseResult]], [[PromiseFulfillReactions]], [[PromiseRejectReactions]], [[PromiseIsHandled]] »).
410        let promise =
411            get_prototype_from_constructor(new_target, StandardConstructors::promise, context)?;
412
413        let promise = JsObject::from_proto_and_data_with_shared_shape(
414            context.root_shape(),
415            promise,
416            // 4. Set promise.[[PromiseState]] to pending.
417            // 5. Set promise.[[PromiseFulfillReactions]] to a new empty List.
418            // 6. Set promise.[[PromiseRejectReactions]] to a new empty List.
419            // 7. Set promise.[[PromiseIsHandled]] to false.
420            Self::new(),
421        );
422
423        // 8. Let resolvingFunctions be CreateResolvingFunctions(promise).
424        let resolving_functions = Self::create_resolving_functions(&promise, context);
425
426        // 9. Let completion Completion(Call(executor, undefined, « resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]] »)be ).
427        let completion = executor.call(
428            &JsValue::undefined(),
429            &[
430                resolving_functions.resolve.clone().into(),
431                resolving_functions.reject.clone().into(),
432            ],
433            context,
434        );
435
436        // 10. If completion is an abrupt completion, then
437        if let Err(e) = completion {
438            let e = e.to_opaque(context);
439            // a. Perform ? Call(resolvingFunctions.[[Reject]], undefined, « completion.[[Value]] »).
440            resolving_functions
441                .reject
442                .call(&JsValue::undefined(), &[e], context)?;
443        }
444
445        // 11. Return promise.
446        promise.conv::<JsValue>().pipe(Ok)
447    }
448}
449
450impl Promise {
451    /// Creates a new, pending `Promise`.
452    pub(crate) fn new() -> Self {
453        Self {
454            state: PromiseState::Pending,
455            fulfill_reactions: Vec::default(),
456            reject_reactions: Vec::default(),
457            handled: false,
458        }
459    }
460
461    /// Gets the current state of the promise.
462    pub(crate) const fn state(&self) -> &PromiseState {
463        &self.state
464    }
465
466    /// [`Promise.try ( callbackfn, ...args )`][spec]
467    ///
468    /// Calls the given function and returns a new promise that is resolved if the function
469    /// completes normally and rejected if it throws.
470    ///
471    /// [spec]: https://tc39.es/proposal-promise-try/#sec-promise.try
472    pub(crate) fn r#try(
473        this: &JsValue,
474        args: &[JsValue],
475        context: &mut Context,
476    ) -> JsResult<JsValue> {
477        let callback = args.get_or_undefined(0);
478        let callback_args = args.get(1..).unwrap_or(&[]);
479
480        // 1. Let C be the this value.
481        // 2. If C is not an Object, throw a TypeError exception.
482        let c = this.as_object().ok_or_else(|| {
483            JsNativeError::typ().with_message("Promise.try() called on a non-object")
484        })?;
485
486        // 3. Let promiseCapability be ? NewPromiseCapability(C).
487        let promise_capability = PromiseCapability::new(&c, context)?;
488
489        // 4. Let status be Completion(Call(callbackfn, undefined, args)).
490        let status = callback.call(&JsValue::undefined(), callback_args, context);
491
492        match status {
493            // 5. If status is an abrupt completion, then
494            Err(err) => {
495                let value = err.to_opaque(context);
496
497                // a. Perform ? Call(promiseCapability.[[Reject]], undefined, « status.[[Value]] »).
498                promise_capability.functions.reject.call(
499                    &JsValue::undefined(),
500                    &[value],
501                    context,
502                )?;
503            }
504            // 6. Else,
505            Ok(value) => {
506                // a. Perform ? Call(promiseCapability.[[Resolve]], undefined, « status.[[Value]] »).
507                promise_capability.functions.resolve.call(
508                    &JsValue::undefined(),
509                    &[value],
510                    context,
511                )?;
512            }
513        }
514
515        // 7. Return promiseCapability.[[Promise]].
516        Ok(promise_capability.promise.clone().into())
517    }
518
519    /// [`Promise.withResolvers ( )`][spec]
520    ///
521    /// Creates a new promise that is pending, and returns that promise plus the resolve and reject
522    /// functions associated with it.
523    ///
524    /// [spec]: https://tc39.es/ecma262/#sec-promise.withResolvers
525    pub(crate) fn with_resolvers(
526        this: &JsValue,
527        _args: &[JsValue],
528        context: &mut Context,
529    ) -> JsResult<JsValue> {
530        // 1. Let C be the this value.
531
532        use super::OrdinaryObject;
533        let c = this.as_object().ok_or_else(|| {
534            JsNativeError::typ().with_message("Promise.withResolvers() called on a non-object")
535        })?;
536
537        // 2. Let promiseCapability be ? NewPromiseCapability(C).
538        let PromiseCapability {
539            promise,
540            functions: ResolvingFunctions { resolve, reject },
541        } = PromiseCapability::new(&c, context)?;
542
543        // 3. Let obj be OrdinaryObjectCreate(%Object.prototype%).
544        // 4. Perform ! CreateDataPropertyOrThrow(obj, "promise", promiseCapability.[[Promise]]).
545        // 5. Perform ! CreateDataPropertyOrThrow(obj, "resolve", promiseCapability.[[Resolve]]).
546        // 6. Perform ! CreateDataPropertyOrThrow(obj, "reject", promiseCapability.[[Reject]]).
547        let obj = context.intrinsics().templates().with_resolvers().create(
548            OrdinaryObject,
549            vec![promise.into(), resolve.into(), reject.into()],
550        );
551
552        // 7. Return obj.
553        Ok(obj.into())
554    }
555
556    /// `Promise.all ( iterable )`
557    ///
558    /// More information:
559    ///  - [ECMAScript reference][spec]
560    ///  - [MDN documentation][mdn]
561    ///
562    /// [spec]: https://tc39.es/ecma262/#sec-promise.all
563    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
564    pub(crate) fn all(
565        this: &JsValue,
566        args: &[JsValue],
567        context: &mut Context,
568    ) -> JsResult<JsValue> {
569        // 1. Let C be the this value.
570        let c = this.as_object().ok_or_else(|| {
571            JsNativeError::typ().with_message("Promise.all() called on a non-object")
572        })?;
573
574        // 2. Let promiseCapability be ? NewPromiseCapability(C).
575        let promise_capability = PromiseCapability::new(&c, context)?;
576
577        // 3. Let promiseResolve be Completion(GetPromiseResolve(C)).
578        let promise_resolve = Self::get_promise_resolve(&c, context);
579
580        // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability).
581        let promise_resolve =
582            if_abrupt_reject_promise!(promise_resolve, promise_capability, context);
583
584        // 5. Let iteratorRecord be Completion(GetIterator(iterable, sync)).
585        let iterator_record = args
586            .get_or_undefined(0)
587            .get_iterator(IteratorHint::Sync, context);
588
589        // 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability).
590        let mut iterator_record =
591            if_abrupt_reject_promise!(iterator_record, promise_capability, context);
592
593        // 7. Let result be Completion(PerformPromiseAll(iteratorRecord, C, promiseCapability, promiseResolve)).
594        let mut result = Self::perform_promise_all(
595            &mut iterator_record,
596            &c,
597            &promise_capability,
598            &promise_resolve,
599            context,
600        )
601        .map(JsValue::from);
602
603        // 8. If result is an abrupt completion, then
604        if result.is_err() {
605            // a. If iteratorRecord.[[Done]] is false, set result to Completion(IteratorClose(iteratorRecord, result)).
606            if !iterator_record.done() {
607                result = iterator_record.close(result, context);
608            }
609
610            // b. IfAbruptRejectPromise(result, promiseCapability).
611            let result = if_abrupt_reject_promise!(result, promise_capability, context);
612
613            return Ok(result);
614        }
615
616        // 9. Return ? result.
617        result
618    }
619
620    /// `PerformPromiseAll ( iteratorRecord, constructor, resultCapability, promiseResolve )`
621    ///
622    /// More information:
623    ///  - [ECMAScript reference][spec]
624    ///
625    /// [spec]: https://tc39.es/ecma262/#sec-performpromiseall
626    pub(crate) fn perform_promise_all(
627        iterator_record: &mut IteratorRecord,
628        constructor: &JsObject,
629        result_capability: &PromiseCapability,
630        promise_resolve: &JsObject,
631        context: &mut Context,
632    ) -> JsResult<JsObject> {
633        #[derive(Debug, Trace, Finalize)]
634        struct ResolveElementCaptures {
635            #[unsafe_ignore_trace]
636            already_called: Rc<Cell<bool>>,
637            index: usize,
638            values: Gc<GcRefCell<Vec<JsValue>>>,
639            capability_resolve: JsFunction,
640            #[unsafe_ignore_trace]
641            remaining_elements_count: Rc<Cell<i32>>,
642        }
643
644        // 1. Let values be a new empty List.
645        let values = Gc::new(GcRefCell::new(Vec::new()));
646
647        // 2. Let remainingElementsCount be the Record { [[Value]]: 1 }.
648        let remaining_elements_count = Rc::new(Cell::new(1));
649
650        // 3. Let index be 0.
651        let mut index = 0;
652
653        // 4. Repeat,
654        while let Some(next) = iterator_record.step_value(context)? {
655            // c. Append undefined to values.
656            values.borrow_mut().push(JsValue::undefined());
657
658            // d. Let nextPromise be ? Call(promiseResolve, constructor, « next »).
659            let next_promise =
660                promise_resolve.call(&constructor.clone().into(), &[next], context)?;
661
662            // e. Let steps be the algorithm steps defined in Promise.all Resolve Element Functions.
663            // f. Let length be the number of non-optional parameters of the function definition in Promise.all Resolve Element Functions.
664            // g. Let onFulfilled be CreateBuiltinFunction(steps, length, "", « [[AlreadyCalled]], [[Index]], [[Values]], [[Capability]], [[RemainingElements]] »).
665            // h. Set onFulfilled.[[AlreadyCalled]] to false.
666            // i. Set onFulfilled.[[Index]] to index.
667            // j. Set onFulfilled.[[Values]] to values.
668            // k. Set onFulfilled.[[Capability]] to resultCapability.
669            // l. Set onFulfilled.[[RemainingElements]] to remainingElementsCount.
670            let on_fulfilled = FunctionObjectBuilder::new(
671                context.realm(),
672                NativeFunction::from_copy_closure_with_captures(
673                    |_, args, captures, context| {
674                        // https://tc39.es/ecma262/#sec-promise.all-resolve-element-functions
675
676                        // 1. Let F be the active function object.
677                        // 2. If F.[[AlreadyCalled]] is true, return undefined.
678                        if captures.already_called.get() {
679                            return Ok(JsValue::undefined());
680                        }
681
682                        // 3. Set F.[[AlreadyCalled]] to true.
683                        captures.already_called.set(true);
684
685                        // 4. Let index be F.[[Index]].
686                        // 5. Let values be F.[[Values]].
687                        // 6. Let promiseCapability be F.[[Capability]].
688                        // 7. Let remainingElementsCount be F.[[RemainingElements]].
689
690                        // 8. Set values[index] to x.
691                        captures.values.borrow_mut()[captures.index] =
692                            args.get_or_undefined(0).clone();
693
694                        // 9. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1.
695                        captures
696                            .remaining_elements_count
697                            .set(captures.remaining_elements_count.get() - 1);
698
699                        // 10. If remainingElementsCount.[[Value]] is 0, then
700                        if captures.remaining_elements_count.get() == 0 {
701                            // a. Let valuesArray be CreateArrayFromList(values).
702                            let values_array = Array::create_array_from_list(
703                                captures.values.borrow().as_slice().iter().cloned(),
704                                context,
705                            );
706
707                            // b. Return ? Call(promiseCapability.[[Resolve]], undefined, « valuesArray »).
708                            return captures.capability_resolve.call(
709                                &JsValue::undefined(),
710                                &[values_array.into()],
711                                context,
712                            );
713                        }
714
715                        // 11. Return undefined.
716                        Ok(JsValue::undefined())
717                    },
718                    ResolveElementCaptures {
719                        already_called: Rc::new(Cell::new(false)),
720                        index,
721                        values: values.clone(),
722                        capability_resolve: result_capability.functions.resolve.clone(),
723                        remaining_elements_count: remaining_elements_count.clone(),
724                    },
725                ),
726            )
727            .name("")
728            .length(1)
729            .constructor(false)
730            .build();
731
732            // m. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] + 1.
733            remaining_elements_count.set(remaining_elements_count.get() + 1);
734
735            // n. Perform ? Invoke(nextPromise, "then", « onFulfilled, resultCapability.[[Reject]] »).
736            next_promise.invoke(
737                js_string!("then"),
738                &[
739                    on_fulfilled.into(),
740                    result_capability.functions.reject.clone().into(),
741                ],
742                context,
743            )?;
744
745            // o. Set index to index + 1.
746            index += 1;
747        }
748
749        // b. If next is done, then
750        //     i. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1.
751        remaining_elements_count.set(remaining_elements_count.get() - 1);
752
753        //     ii. If remainingElementsCount.[[Value]] = 0, then
754        if remaining_elements_count.get() == 0 {
755            // 1. Let valuesArray be CreateArrayFromList(values).
756            let values_array =
757                Array::create_array_from_list(values.borrow().iter().cloned(), context);
758
759            // 2. Perform ? Call(resultCapability.[[Resolve]], undefined, « valuesArray »).
760            result_capability.functions.resolve.call(
761                &JsValue::undefined(),
762                &[values_array.into()],
763                context,
764            )?;
765        }
766
767        //     iii. Return resultCapability.[[Promise]].
768        Ok(result_capability.promise.clone())
769    }
770
771    /// `Promise.allSettled ( iterable )`
772    ///
773    /// More information:
774    ///  - [ECMAScript reference][spec]
775    ///  - [MDN documentation][mdn]
776    ///
777    /// [spec]: https://tc39.es/ecma262/#sec-promise.allsettled
778    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled
779    pub(crate) fn all_settled(
780        this: &JsValue,
781        args: &[JsValue],
782        context: &mut Context,
783    ) -> JsResult<JsValue> {
784        // 1. Let C be the this value.
785        let c = this.as_object().ok_or_else(|| {
786            JsNativeError::typ().with_message("Promise.allSettled() called on a non-object")
787        })?;
788
789        // 2. Let promiseCapability be ? NewPromiseCapability(C).
790        let promise_capability = PromiseCapability::new(&c, context)?;
791
792        // 3. Let promiseResolve be Completion(GetPromiseResolve(C)).
793        let promise_resolve = Self::get_promise_resolve(&c, context);
794
795        // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability).
796        let promise_resolve =
797            if_abrupt_reject_promise!(promise_resolve, promise_capability, context);
798
799        // 5. Let iteratorRecord be Completion(GetIterator(iterable, sync)).
800        let iterator_record = args
801            .get_or_undefined(0)
802            .get_iterator(IteratorHint::Sync, context);
803
804        // 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability).
805        let mut iterator_record =
806            if_abrupt_reject_promise!(iterator_record, promise_capability, context);
807
808        // 7. Let result be Completion(PerformPromiseAllSettled(iteratorRecord, C, promiseCapability, promiseResolve)).
809        let mut result = Self::perform_promise_all_settled(
810            &mut iterator_record,
811            &c,
812            &promise_capability,
813            &promise_resolve,
814            context,
815        )
816        .map(JsValue::from);
817
818        // 8. If result is an abrupt completion, then
819        if result.is_err() {
820            // a. If iteratorRecord.[[Done]] is false, set result to Completion(IteratorClose(iteratorRecord, result)).
821            if !iterator_record.done() {
822                result = iterator_record.close(result, context);
823            }
824
825            // b. IfAbruptRejectPromise(result, promiseCapability).
826            let result = if_abrupt_reject_promise!(result, promise_capability, context);
827
828            return Ok(result);
829        }
830
831        // 9. Return ? result.
832        result
833    }
834
835    /// `PerformPromiseAllSettled ( iteratorRecord, constructor, resultCapability, promiseResolve )`
836    ///
837    /// More information:
838    ///  - [ECMAScript reference][spec]
839    ///
840    /// [spec]: https://tc39.es/ecma262/#sec-performpromiseallsettled
841    pub(crate) fn perform_promise_all_settled(
842        iterator_record: &mut IteratorRecord,
843        constructor: &JsObject,
844        result_capability: &PromiseCapability,
845        promise_resolve: &JsObject,
846        context: &mut Context,
847    ) -> JsResult<JsObject> {
848        #[derive(Debug, Trace, Finalize)]
849        struct ResolveRejectElementCaptures {
850            #[unsafe_ignore_trace]
851            already_called: Rc<Cell<bool>>,
852            index: usize,
853            values: Gc<GcRefCell<Vec<JsValue>>>,
854            capability: JsFunction,
855            #[unsafe_ignore_trace]
856            remaining_elements: Rc<Cell<i32>>,
857        }
858
859        // 1. Let values be a new empty List.
860        let values = Gc::new(GcRefCell::new(Vec::new()));
861
862        // 2. Let remainingElementsCount be the Record { [[Value]]: 1 }.
863        let remaining_elements_count = Rc::new(Cell::new(1));
864
865        // 3. Let index be 0.
866        let mut index = 0;
867
868        // 4. Repeat,
869        while let Some(next) = iterator_record.step_value(context)? {
870            // c. Append undefined to values.
871            values.borrow_mut().push(JsValue::undefined());
872
873            // d. Let nextPromise be ? Call(promiseResolve, constructor, « next »).
874            let next_promise =
875                promise_resolve.call(&constructor.clone().into(), &[next], context)?;
876
877            // e. Let stepsFulfilled be the algorithm steps defined in Promise.allSettled Resolve Element Functions.
878            // f. Let lengthFulfilled be the number of non-optional parameters of the function definition in Promise.allSettled Resolve Element Functions.
879            // g. Let onFulfilled be CreateBuiltinFunction(stepsFulfilled, lengthFulfilled, "", « [[AlreadyCalled]], [[Index]], [[Values]], [[Capability]], [[RemainingElements]] »).
880            // h. Let alreadyCalled be the Record { [[Value]]: false }.
881            // i. Set onFulfilled.[[AlreadyCalled]] to alreadyCalled.
882            // j. Set onFulfilled.[[Index]] to index.
883            // k. Set onFulfilled.[[Values]] to values.
884            // l. Set onFulfilled.[[Capability]] to resultCapability.
885            // m. Set onFulfilled.[[RemainingElements]] to remainingElementsCount.
886            let on_fulfilled = FunctionObjectBuilder::new(
887                context.realm(),
888                NativeFunction::from_copy_closure_with_captures(
889                    |_, args, captures, context| {
890                        // https://tc39.es/ecma262/#sec-promise.allsettled-resolve-element-functions
891
892                        // 1. Let F be the active function object.
893                        // 2. Let alreadyCalled be F.[[AlreadyCalled]].
894
895                        // 3. If alreadyCalled.[[Value]] is true, return undefined.
896                        if captures.already_called.get() {
897                            return Ok(JsValue::undefined());
898                        }
899
900                        // 4. Set alreadyCalled.[[Value]] to true.
901                        captures.already_called.set(true);
902
903                        // 5. Let index be F.[[Index]].
904                        // 6. Let values be F.[[Values]].
905                        // 7. Let promiseCapability be F.[[Capability]].
906                        // 8. Let remainingElementsCount be F.[[RemainingElements]].
907
908                        // 9. Let obj be OrdinaryObjectCreate(%Object.prototype%).
909                        let obj = JsObject::with_object_proto(context.intrinsics());
910
911                        // 10. Perform ! CreateDataPropertyOrThrow(obj, "status", "fulfilled").
912                        obj.create_data_property_or_throw(
913                            js_string!("status"),
914                            js_string!("fulfilled"),
915                            context,
916                        )
917                        .expect("cannot fail per spec");
918
919                        // 11. Perform ! CreateDataPropertyOrThrow(obj, "value", x).
920                        obj.create_data_property_or_throw(
921                            js_string!("value"),
922                            args.get_or_undefined(0).clone(),
923                            context,
924                        )
925                        .expect("cannot fail per spec");
926
927                        // 12. Set values[index] to obj.
928                        captures.values.borrow_mut()[captures.index] = obj.into();
929
930                        // 13. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1.
931                        captures
932                            .remaining_elements
933                            .set(captures.remaining_elements.get() - 1);
934
935                        // 14. If remainingElementsCount.[[Value]] is 0, then
936                        if captures.remaining_elements.get() == 0 {
937                            // a. Let valuesArray be CreateArrayFromList(values).
938                            let values_array = Array::create_array_from_list(
939                                captures.values.borrow().as_slice().iter().cloned(),
940                                context,
941                            );
942
943                            // b. Return ? Call(promiseCapability.[[Resolve]], undefined, « valuesArray »).
944                            return captures.capability.call(
945                                &JsValue::undefined(),
946                                &[values_array.into()],
947                                context,
948                            );
949                        }
950
951                        // 15. Return undefined.
952                        Ok(JsValue::undefined())
953                    },
954                    ResolveRejectElementCaptures {
955                        already_called: Rc::new(Cell::new(false)),
956                        index,
957                        values: values.clone(),
958                        capability: result_capability.functions.resolve.clone(),
959                        remaining_elements: remaining_elements_count.clone(),
960                    },
961                ),
962            )
963            .name("")
964            .length(1)
965            .constructor(false)
966            .build();
967
968            // n. Let stepsRejected be the algorithm steps defined in Promise.allSettled Reject Element Functions.
969            // o. Let lengthRejected be the number of non-optional parameters of the function definition in Promise.allSettled Reject Element Functions.
970            // p. Let onRejected be CreateBuiltinFunction(stepsRejected, lengthRejected, "", « [[AlreadyCalled]], [[Index]], [[Values]], [[Capability]], [[RemainingElements]] »).
971            // q. Set onRejected.[[AlreadyCalled]] to alreadyCalled.
972            // r. Set onRejected.[[Index]] to index.
973            // s. Set onRejected.[[Values]] to values.
974            // t. Set onRejected.[[Capability]] to resultCapability.
975            // u. Set onRejected.[[RemainingElements]] to remainingElementsCount.
976            let on_rejected = FunctionObjectBuilder::new(
977                context.realm(),
978                NativeFunction::from_copy_closure_with_captures(
979                    |_, args, captures, context| {
980                        // https://tc39.es/ecma262/#sec-promise.allsettled-reject-element-functions
981
982                        // 1. Let F be the active function object.
983                        // 2. Let alreadyCalled be F.[[AlreadyCalled]].
984
985                        // 3. If alreadyCalled.[[Value]] is true, return undefined.
986                        if captures.already_called.get() {
987                            return Ok(JsValue::undefined());
988                        }
989
990                        // 4. Set alreadyCalled.[[Value]] to true.
991                        captures.already_called.set(true);
992
993                        // 5. Let index be F.[[Index]].
994                        // 6. Let values be F.[[Values]].
995                        // 7. Let promiseCapability be F.[[Capability]].
996                        // 8. Let remainingElementsCount be F.[[RemainingElements]].
997
998                        // 9. Let obj be OrdinaryObjectCreate(%Object.prototype%).
999                        let obj = JsObject::with_object_proto(context.intrinsics());
1000
1001                        // 10. Perform ! CreateDataPropertyOrThrow(obj, "status", "rejected").
1002                        obj.create_data_property_or_throw(
1003                            js_string!("status"),
1004                            js_string!("rejected"),
1005                            context,
1006                        )
1007                        .expect("cannot fail per spec");
1008
1009                        // 11. Perform ! CreateDataPropertyOrThrow(obj, "reason", x).
1010                        obj.create_data_property_or_throw(
1011                            js_string!("reason"),
1012                            args.get_or_undefined(0).clone(),
1013                            context,
1014                        )
1015                        .expect("cannot fail per spec");
1016
1017                        // 12. Set values[index] to obj.
1018                        captures.values.borrow_mut()[captures.index] = obj.into();
1019
1020                        // 13. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1.
1021                        captures
1022                            .remaining_elements
1023                            .set(captures.remaining_elements.get() - 1);
1024
1025                        // 14. If remainingElementsCount.[[Value]] is 0, then
1026                        if captures.remaining_elements.get() == 0 {
1027                            // a. Let valuesArray be CreateArrayFromList(values).
1028                            let values_array = Array::create_array_from_list(
1029                                captures.values.borrow().as_slice().iter().cloned(),
1030                                context,
1031                            );
1032
1033                            // b. Return ? Call(promiseCapability.[[Resolve]], undefined, « valuesArray »).
1034                            return captures.capability.call(
1035                                &JsValue::undefined(),
1036                                &[values_array.into()],
1037                                context,
1038                            );
1039                        }
1040
1041                        // 15. Return undefined.
1042                        Ok(JsValue::undefined())
1043                    },
1044                    ResolveRejectElementCaptures {
1045                        already_called: Rc::new(Cell::new(false)),
1046                        index,
1047                        values: values.clone(),
1048                        capability: result_capability.functions.resolve.clone(),
1049                        remaining_elements: remaining_elements_count.clone(),
1050                    },
1051                ),
1052            )
1053            .name("")
1054            .length(1)
1055            .constructor(false)
1056            .build();
1057
1058            // v. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] + 1.
1059            remaining_elements_count.set(remaining_elements_count.get() + 1);
1060
1061            // w. Perform ? Invoke(nextPromise, "then", « onFulfilled, onRejected »).
1062            next_promise.invoke(
1063                js_string!("then"),
1064                &[on_fulfilled.into(), on_rejected.into()],
1065                context,
1066            )?;
1067
1068            // x. Set index to index + 1.
1069            index += 1;
1070        }
1071
1072        // b. If next is done, then
1073        //     i. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1.
1074        remaining_elements_count.set(remaining_elements_count.get() - 1);
1075
1076        //     ii. If remainingElementsCount.[[Value]] = 0, then
1077        if remaining_elements_count.get() == 0 {
1078            // 1. Let valuesArray be CreateArrayFromList(values).
1079            let values_array =
1080                Array::create_array_from_list(values.borrow().as_slice().iter().cloned(), context);
1081
1082            // 2. Perform ? Call(resultCapability.[[Resolve]], undefined, « valuesArray »).
1083            result_capability.functions.resolve.call(
1084                &JsValue::undefined(),
1085                &[values_array.into()],
1086                context,
1087            )?;
1088        }
1089
1090        //     iii. Return resultCapability.[[Promise]].
1091        Ok(result_capability.promise.clone())
1092    }
1093
1094    /// `Promise.any ( iterable )`
1095    ///
1096    /// More information:
1097    ///  - [ECMAScript reference][spec]
1098    ///  - [MDN documentation][mdn]
1099    ///
1100    /// [spec]: https://tc39.es/ecma262/#sec-promise.any
1101    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/any
1102    pub(crate) fn any(
1103        this: &JsValue,
1104        args: &[JsValue],
1105        context: &mut Context,
1106    ) -> JsResult<JsValue> {
1107        // 1. Let C be the this value.
1108        let c = this.as_object().ok_or_else(|| {
1109            JsNativeError::typ().with_message("Promise.any() called on a non-object")
1110        })?;
1111
1112        // 2. Let promiseCapability be ? NewPromiseCapability(C).
1113        let promise_capability = PromiseCapability::new(&c, context)?;
1114
1115        // 3. Let promiseResolve be Completion(GetPromiseResolve(C)).
1116        let promise_resolve = Self::get_promise_resolve(&c, context);
1117
1118        // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability).
1119        let promise_resolve =
1120            if_abrupt_reject_promise!(promise_resolve, promise_capability, context);
1121
1122        // 5. Let iteratorRecord be Completion(GetIterator(iterable, sync)).
1123        let iterator_record = args
1124            .get_or_undefined(0)
1125            .get_iterator(IteratorHint::Sync, context);
1126
1127        // 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability).
1128        let mut iterator_record =
1129            if_abrupt_reject_promise!(iterator_record, promise_capability, context);
1130
1131        // 7. Let result be Completion(PerformPromiseAny(iteratorRecord, C, promiseCapability, promiseResolve)).
1132        let mut result = Self::perform_promise_any(
1133            &mut iterator_record,
1134            &c,
1135            &promise_capability,
1136            &promise_resolve,
1137            context,
1138        )
1139        .map(JsValue::from);
1140
1141        // 8. If result is an abrupt completion, then
1142        if result.is_err() {
1143            // a. If iteratorRecord.[[Done]] is false, set result to Completion(IteratorClose(iteratorRecord, result)).
1144            if !iterator_record.done() {
1145                result = iterator_record.close(result, context);
1146            }
1147
1148            // b. IfAbruptRejectPromise(result, promiseCapability).
1149            let result = if_abrupt_reject_promise!(result, promise_capability, context);
1150
1151            return Ok(result);
1152        }
1153
1154        // 9. Return ? result.
1155        result
1156    }
1157
1158    /// `PerformPromiseAny ( iteratorRecord, constructor, resultCapability, promiseResolve )`
1159    ///
1160    /// More information:
1161    ///  - [ECMAScript reference][spec]
1162    ///
1163    /// [spec]: https://tc39.es/ecma262/#sec-performpromiseany
1164    pub(crate) fn perform_promise_any(
1165        iterator_record: &mut IteratorRecord,
1166        constructor: &JsObject,
1167        result_capability: &PromiseCapability,
1168        promise_resolve: &JsObject,
1169        context: &mut Context,
1170    ) -> JsResult<JsObject> {
1171        #[derive(Debug, Trace, Finalize)]
1172        struct RejectElementCaptures {
1173            #[unsafe_ignore_trace]
1174            already_called: Rc<Cell<bool>>,
1175            index: usize,
1176            errors: Gc<GcRefCell<Vec<JsValue>>>,
1177            capability_reject: JsFunction,
1178            #[unsafe_ignore_trace]
1179            remaining_elements_count: Rc<Cell<i32>>,
1180        }
1181
1182        // 1. Let errors be a new empty List.
1183        let errors = Gc::new(GcRefCell::new(Vec::new()));
1184
1185        // 2. Let remainingElementsCount be the Record { [[Value]]: 1 }.
1186        let remaining_elements_count = Rc::new(Cell::new(1));
1187
1188        // 3. Let index be 0.
1189        let mut index = 0;
1190
1191        // 4. Repeat,
1192        //     a. Let next be ? IteratorStepValue(iteratorRecord).
1193        while let Some(next) = iterator_record.step_value(context)? {
1194            // c. Append undefined to errors.
1195            errors.borrow_mut().push(JsValue::undefined());
1196
1197            // d. Let nextPromise be ? Call(promiseResolve, constructor, « next »).
1198            let next_promise =
1199                promise_resolve.call(&constructor.clone().into(), &[next], context)?;
1200
1201            // e. Let stepsRejected be the algorithm steps defined in Promise.any Reject Element Functions.
1202            // f. Let lengthRejected be the number of non-optional parameters of the function definition in Promise.any Reject Element Functions.
1203            // g. Let onRejected be CreateBuiltinFunction(stepsRejected, lengthRejected, "", « [[AlreadyCalled]], [[Index]], [[Errors]], [[Capability]], [[RemainingElements]] »).
1204            // h. Set onRejected.[[AlreadyCalled]] to false.
1205            // i. Set onRejected.[[Index]] to index.
1206            // j. Set onRejected.[[Errors]] to errors.
1207            // k. Set onRejected.[[Capability]] to resultCapability.
1208            // l. Set onRejected.[[RemainingElements]] to remainingElementsCount.
1209            let on_rejected = FunctionObjectBuilder::new(
1210                context.realm(),
1211                NativeFunction::from_copy_closure_with_captures(
1212                    |_, args, captures, context| {
1213                        // https://tc39.es/ecma262/#sec-promise.any-reject-element-functions
1214
1215                        // 1. Let F be the active function object.
1216
1217                        // 2. If F.[[AlreadyCalled]] is true, return undefined.
1218                        if captures.already_called.get() {
1219                            return Ok(JsValue::undefined());
1220                        }
1221
1222                        // 3. Set F.[[AlreadyCalled]] to true.
1223                        captures.already_called.set(true);
1224
1225                        // 4. Let index be F.[[Index]].
1226                        // 5. Let errors be F.[[Errors]].
1227                        // 6. Let promiseCapability be F.[[Capability]].
1228                        // 7. Let remainingElementsCount be F.[[RemainingElements]].
1229
1230                        // 8. Set errors[index] to x.
1231                        captures.errors.borrow_mut()[captures.index] =
1232                            args.get_or_undefined(0).clone();
1233
1234                        // 9. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1.
1235                        captures
1236                            .remaining_elements_count
1237                            .set(captures.remaining_elements_count.get() - 1);
1238
1239                        // 10. If remainingElementsCount.[[Value]] is 0, then
1240                        if captures.remaining_elements_count.get() == 0 {
1241                            // a. Let error be a newly created AggregateError object.
1242                            // b. Perform ! DefinePropertyOrThrow(error, "errors", PropertyDescriptor { [[Configurable]]: true, [[Enumerable]]: false, [[Writable]]: true, [[Value]]: CreateArrayFromList(errors) }).
1243                            let error = JsNativeError::aggregate(
1244                                captures
1245                                    .errors
1246                                    .borrow()
1247                                    .iter()
1248                                    .cloned()
1249                                    .map(JsError::from_opaque)
1250                                    .collect(),
1251                            )
1252                            .with_message("no promise in Promise.any was fulfilled.");
1253
1254                            // c. Return ? Call(promiseCapability.[[Reject]], undefined, « error »).
1255                            return captures.capability_reject.call(
1256                                &JsValue::undefined(),
1257                                &[error.to_opaque(context).into()],
1258                                context,
1259                            );
1260                        }
1261
1262                        // 11. Return undefined.
1263                        Ok(JsValue::undefined())
1264                    },
1265                    RejectElementCaptures {
1266                        already_called: Rc::new(Cell::new(false)),
1267                        index,
1268                        errors: errors.clone(),
1269                        capability_reject: result_capability.functions.reject.clone(),
1270                        remaining_elements_count: remaining_elements_count.clone(),
1271                    },
1272                ),
1273            )
1274            .name("")
1275            .length(1)
1276            .constructor(false)
1277            .build();
1278
1279            // m. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] + 1.
1280            remaining_elements_count.set(remaining_elements_count.get() + 1);
1281
1282            // n. Perform ? Invoke(nextPromise, "then", « resultCapability.[[Resolve]], onRejected »).
1283            next_promise.invoke(
1284                js_string!("then"),
1285                &[
1286                    result_capability.functions.resolve.clone().into(),
1287                    on_rejected.into(),
1288                ],
1289                context,
1290            )?;
1291
1292            // o. Set index to index + 1.
1293            index += 1;
1294        }
1295
1296        //     b. If next is done, then
1297        //         i. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1.
1298        remaining_elements_count.set(remaining_elements_count.get() - 1);
1299        //         ii. If remainingElementsCount.[[Value]] = 0, then
1300        if remaining_elements_count.get() == 0 {
1301            // 1. Let error be a newly created AggregateError object.
1302            let error = JsNativeError::aggregate(
1303                errors
1304                    .borrow()
1305                    .iter()
1306                    .cloned()
1307                    .map(JsError::from_opaque)
1308                    .collect(),
1309            )
1310            .with_message("no promise in Promise.any was fulfilled.");
1311
1312            // 2. Perform ! DefinePropertyOrThrow(error, "errors", PropertyDescriptor { [[Configurable]]: true, [[Enumerable]]: false, [[Writable]]: true, [[Value]]: CreateArrayFromList(errors) }).
1313            // 3. Return ThrowCompletion(error).
1314            return Err(error.into());
1315        }
1316
1317        //         iii. Return resultCapability.[[Promise]].
1318        Ok(result_capability.promise.clone())
1319    }
1320
1321    /// `Promise.race ( iterable )`
1322    ///
1323    /// The `race` function returns a new promise which is settled in the same way as the first
1324    /// passed promise to settle. It resolves all elements of the passed `iterable` to promises.
1325    ///
1326    /// More information:
1327    ///  - [ECMAScript reference][spec]
1328    ///  - [MDN documentation][mdn]
1329    ///
1330    /// [spec]: https://tc39.es/ecma262/#sec-promise.race
1331    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race
1332    pub(crate) fn race(
1333        this: &JsValue,
1334        args: &[JsValue],
1335        context: &mut Context,
1336    ) -> JsResult<JsValue> {
1337        let iterable = args.get_or_undefined(0);
1338
1339        // 1. Let C be the this value.
1340        let c = this.as_object().ok_or_else(|| {
1341            JsNativeError::typ().with_message("Promise.race() called on a non-object")
1342        })?;
1343
1344        // 2. Let promiseCapability be ? NewPromiseCapability(C).
1345        let promise_capability = PromiseCapability::new(&c, context)?;
1346
1347        // 3. Let promiseResolve be Completion(GetPromiseResolve(C)).
1348        let promise_resolve = Self::get_promise_resolve(&c, context);
1349
1350        // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability).
1351        let promise_resolve =
1352            if_abrupt_reject_promise!(promise_resolve, promise_capability, context);
1353
1354        // 5. Let iteratorRecord be Completion(GetIterator(iterable, sync)).
1355        let iterator_record = iterable.get_iterator(IteratorHint::Sync, context);
1356
1357        // 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability).
1358        let mut iterator_record =
1359            if_abrupt_reject_promise!(iterator_record, promise_capability, context);
1360
1361        // 7. Let result be Completion(PerformPromiseRace(iteratorRecord, C, promiseCapability, promiseResolve)).
1362        let mut result = Self::perform_promise_race(
1363            &mut iterator_record,
1364            &c,
1365            &promise_capability,
1366            &promise_resolve,
1367            context,
1368        )
1369        .map(JsValue::from);
1370
1371        // 8. If result is an abrupt completion, then
1372        if result.is_err() {
1373            // a. If iteratorRecord.[[Done]] is false, set result to Completion(IteratorClose(iteratorRecord, result)).
1374            if !iterator_record.done() {
1375                result = iterator_record.close(result, context);
1376            }
1377
1378            // b. IfAbruptRejectPromise(result, promiseCapability).
1379            let result = if_abrupt_reject_promise!(result, promise_capability, context);
1380
1381            Ok(result)
1382        } else {
1383            // 9. Return ? result.
1384            result
1385        }
1386    }
1387
1388    /// `PerformPromiseRace ( iteratorRecord, constructor, resultCapability, promiseResolve )`
1389    ///
1390    /// The abstract operation `PerformPromiseRace` takes arguments `iteratorRecord`, `constructor`
1391    /// (a constructor), `resultCapability` (a [`PromiseCapability`] Record), and `promiseResolve`
1392    /// (a function object) and returns either a normal completion containing an ECMAScript
1393    /// language value or a throw completion.
1394    ///
1395    /// More information:
1396    ///  - [ECMAScript reference][spec]
1397    ///
1398    /// [spec]: https://tc39.es/ecma262/#sec-performpromiserace
1399    pub(crate) fn perform_promise_race(
1400        iterator_record: &mut IteratorRecord,
1401        constructor: &JsObject,
1402        result_capability: &PromiseCapability,
1403        promise_resolve: &JsObject,
1404        context: &mut Context,
1405    ) -> JsResult<JsObject> {
1406        let constructor = constructor.clone().into();
1407
1408        // 1. Repeat,
1409        //     a. Let next be ? IteratorStepValue(iteratorRecord).
1410        while let Some(next) = iterator_record.step_value(context)? {
1411            // c. Let nextPromise be ? Call(promiseResolve, constructor, « next »).
1412            let next_promise = promise_resolve.call(&constructor, &[next], context)?;
1413            // d. Perform ? Invoke(nextPromise, "then", « resultCapability.[[Resolve]], resultCapability.[[Reject]] »).
1414            next_promise.invoke(
1415                js_string!("then"),
1416                &[
1417                    result_capability.functions.resolve.clone().into(),
1418                    result_capability.functions.reject.clone().into(),
1419                ],
1420                context,
1421            )?;
1422        }
1423
1424        //     b. If next is done, then
1425        //         i. Return resultCapability.[[Promise]].
1426        Ok(result_capability.promise.clone())
1427    }
1428
1429    /// `Promise.reject ( r )`
1430    ///
1431    /// More information:
1432    ///  - [ECMAScript reference][spec]
1433    ///  - [MDN documentation][mdn]
1434    ///
1435    /// [spec]: https://tc39.es/ecma262/#sec-promise.reject
1436    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/reject
1437    pub(crate) fn reject(
1438        this: &JsValue,
1439        args: &[JsValue],
1440        context: &mut Context,
1441    ) -> JsResult<JsValue> {
1442        let r = args.get_or_undefined(0).clone();
1443
1444        // 1. Let C be the this value.
1445        let c = this.as_object().ok_or_else(|| {
1446            JsNativeError::typ().with_message("Promise.reject() called on a non-object")
1447        })?;
1448
1449        Self::promise_reject(&c, &JsError::from_opaque(r), context).map(JsValue::from)
1450    }
1451
1452    /// Utility function to create a rejected promise.
1453    pub(crate) fn promise_reject(
1454        c: &JsObject,
1455        e: &JsError,
1456        context: &mut Context,
1457    ) -> JsResult<JsObject> {
1458        let e = e.to_opaque(context);
1459
1460        // 2. Let promiseCapability be ? NewPromiseCapability(C).
1461        let promise_capability = PromiseCapability::new(c, context)?;
1462
1463        // 3. Perform ? Call(promiseCapability.[[Reject]], undefined, « r »).
1464        promise_capability
1465            .functions
1466            .reject
1467            .call(&JsValue::undefined(), &[e], context)?;
1468
1469        // 4. Return promiseCapability.[[Promise]].
1470        Ok(promise_capability.promise.clone())
1471    }
1472
1473    /// `Promise.resolve ( x )`
1474    ///
1475    /// More information:
1476    ///  - [ECMAScript reference][spec]
1477    ///  - [MDN documentation][mdn]
1478    ///
1479    /// [spec]: https://tc39.es/ecma262/#sec-promise.resolve
1480    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve
1481    pub(crate) fn resolve(
1482        this: &JsValue,
1483        args: &[JsValue],
1484        context: &mut Context,
1485    ) -> JsResult<JsValue> {
1486        let x = args.get_or_undefined(0);
1487
1488        // 1. Let C be the this value.
1489        // 2. If Type(C) is not Object, throw a TypeError exception.
1490        let c = this.as_object().ok_or_else(|| {
1491            JsNativeError::typ().with_message("Promise.resolve() called on a non-object")
1492        })?;
1493
1494        // 3. Return ? PromiseResolve(C, x).
1495        Self::promise_resolve(&c, x.clone(), context).map(JsValue::from)
1496    }
1497
1498    /// `PromiseResolve ( C, x )`
1499    ///
1500    /// The abstract operation `PromiseResolve` takes arguments `C` (a constructor) and `x` (an
1501    /// ECMAScript language value) and returns either a normal completion containing an ECMAScript
1502    /// language value or a throw completion. It returns a new promise resolved with `x`.
1503    ///
1504    /// More information:
1505    ///  - [ECMAScript reference][spec]
1506    ///
1507    /// [spec]: https://tc39.es/ecma262/#sec-promise-resolve
1508    pub(crate) fn promise_resolve(
1509        c: &JsObject,
1510        x: JsValue,
1511        context: &mut Context,
1512    ) -> JsResult<JsObject> {
1513        // 1. If IsPromise(x) is true, then
1514        if let Some(x) = x.as_promise_object() {
1515            // a. Let xConstructor be ? Get(x, "constructor").
1516            let x_constructor = x.get(CONSTRUCTOR, context)?;
1517            // b. If SameValue(xConstructor, C) is true, return x.
1518            if x_constructor
1519                .as_object()
1520                .is_some_and(|o| JsObject::equals(&o, c))
1521            {
1522                return Ok(x.clone());
1523            }
1524        }
1525
1526        // 2. Let promiseCapability be ? NewPromiseCapability(C).
1527        let promise_capability = PromiseCapability::new(&c.clone(), context)?;
1528
1529        // 3. Perform ? Call(promiseCapability.[[Resolve]], undefined, « x »).
1530        promise_capability
1531            .functions
1532            .resolve
1533            .call(&JsValue::undefined(), &[x], context)?;
1534
1535        // 4. Return promiseCapability.[[Promise]].
1536        Ok(promise_capability.promise.clone())
1537    }
1538
1539    /// `get Promise [ @@species ]`
1540    ///
1541    /// The `Promise [ @@species ]` accessor property returns the Promise constructor.
1542    ///
1543    /// More information:
1544    ///  - [ECMAScript reference][spec]
1545    ///  - [MDN documentation][mdn]
1546    ///
1547    /// [spec]: https://tc39.es/ecma262/#sec-get-promise-@@species
1548    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/@@species
1549    #[allow(clippy::unnecessary_wraps)]
1550    fn get_species(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
1551        // 1. Return the this value.
1552        Ok(this.clone())
1553    }
1554
1555    /// `Promise.prototype.catch ( onRejected )`
1556    ///
1557    /// More information:
1558    ///  - [ECMAScript reference][spec]
1559    ///  - [MDN documentation][mdn]
1560    ///
1561    /// [spec]: https://tc39.es/ecma262/#sec-promise.prototype.catch
1562    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch
1563    pub(crate) fn catch(
1564        this: &JsValue,
1565        args: &[JsValue],
1566        context: &mut Context,
1567    ) -> JsResult<JsValue> {
1568        let on_rejected = args.get_or_undefined(0);
1569
1570        // 1. Let promise be the this value.
1571        let promise = this;
1572        // 2. Return ? Invoke(promise, "then", « undefined, onRejected »).
1573        promise.invoke(
1574            js_string!("then"),
1575            &[JsValue::undefined(), on_rejected.clone()],
1576            context,
1577        )
1578    }
1579
1580    /// `Promise.prototype.finally ( onFinally )`
1581    ///
1582    /// More information:
1583    ///  - [ECMAScript reference][spec]
1584    ///  - [MDN documentation][mdn]
1585    ///
1586    /// [spec]: https://tc39.es/ecma262/#sec-promise.prototype.finally
1587    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/finally
1588    pub(crate) fn finally(
1589        this: &JsValue,
1590        args: &[JsValue],
1591        context: &mut Context,
1592    ) -> JsResult<JsValue> {
1593        // 1. Let promise be the this value.
1594        let promise = this;
1595
1596        // 2. If Type(promise) is not Object, throw a TypeError exception.
1597        let Some(promise) = promise.as_object() else {
1598            return Err(JsNativeError::typ()
1599                .with_message("finally called with a non-object promise")
1600                .into());
1601        };
1602
1603        // 3. Let C be ? SpeciesConstructor(promise, %Promise%).
1604        let c = promise.species_constructor(StandardConstructors::promise, context)?;
1605
1606        // 4. Assert: IsConstructor(C) is true.
1607        debug_assert!(c.is_constructor());
1608
1609        let on_finally = args.get_or_undefined(0);
1610
1611        let Some(on_finally) = on_finally.as_object().and_then(JsFunction::from_object) else {
1612            // 5. If IsCallable(onFinally) is false, then
1613            //    a. Let thenFinally be onFinally.
1614            //    b. Let catchFinally be onFinally.
1615            // 7. Return ? Invoke(promise, "then", « thenFinally, catchFinally »).
1616            let then = promise.get(js_string!("then"), context)?;
1617            return then.call(this, &[on_finally.clone(), on_finally.clone()], context);
1618        };
1619
1620        let (then_finally, catch_finally) =
1621            Self::then_catch_finally_closures(c, on_finally, context);
1622
1623        // 7. Return ? Invoke(promise, "then", « thenFinally, catchFinally »).
1624        let then = promise.get(js_string!("then"), context)?;
1625        then.call(this, &[then_finally.into(), catch_finally.into()], context)
1626    }
1627
1628    pub(crate) fn then_catch_finally_closures(
1629        c: JsObject,
1630        on_finally: JsFunction,
1631        context: &mut Context,
1632    ) -> (JsFunction, JsFunction) {
1633        /// Capture object for the `thenFinallyClosure` abstract closure.
1634        #[derive(Debug, Trace, Finalize)]
1635        struct FinallyCaptures {
1636            on_finally: JsFunction,
1637            c: JsObject,
1638        }
1639
1640        // a. Let thenFinallyClosure be a new Abstract Closure with parameters (value) that captures onFinally and C and performs the following steps when called:
1641        let then_finally_closure = FunctionObjectBuilder::new(
1642            context.realm(),
1643            NativeFunction::from_copy_closure_with_captures(
1644                |_this, args, captures, context| {
1645                    /// Capture object for the abstract `returnValue` closure.
1646                    #[derive(Debug, Trace, Finalize)]
1647                    struct ReturnValueCaptures {
1648                        value: JsValue,
1649                    }
1650
1651                    let value = args.get_or_undefined(0);
1652
1653                    // i. Let result be ? Call(onFinally, undefined).
1654                    let result = captures
1655                        .on_finally
1656                        .call(&JsValue::undefined(), &[], context)?;
1657
1658                    // ii. Let promise be ? PromiseResolve(C, result).
1659                    let promise = Self::promise_resolve(&captures.c, result, context)?;
1660
1661                    // iii. Let returnValue be a new Abstract Closure with no parameters that captures value and performs the following steps when called:
1662                    let return_value = FunctionObjectBuilder::new(
1663                        context.realm(),
1664                        NativeFunction::from_copy_closure_with_captures(
1665                            |_this, _args, captures, _context| {
1666                                // 1. Return value.
1667                                Ok(captures.value.clone())
1668                            },
1669                            ReturnValueCaptures {
1670                                value: value.clone(),
1671                            },
1672                        ),
1673                    );
1674
1675                    // iv. Let valueThunk be CreateBuiltinFunction(returnValue, 0, "", « »).
1676                    let value_thunk = return_value.length(0).name("").build();
1677
1678                    // v. Return ? Invoke(promise, "then", « valueThunk »).
1679                    promise.invoke(js_string!("then"), &[value_thunk.into()], context)
1680                },
1681                FinallyCaptures {
1682                    on_finally: on_finally.clone(),
1683                    c: c.clone(),
1684                },
1685            ),
1686        );
1687
1688        // b. Let thenFinally be CreateBuiltinFunction(thenFinallyClosure, 1, "", « »).
1689        let then_finally = then_finally_closure.length(1).name("").build();
1690
1691        // c. Let catchFinallyClosure be a new Abstract Closure with parameters (reason) that captures onFinally and C and performs the following steps when called:
1692        let catch_finally_closure = FunctionObjectBuilder::new(
1693            context.realm(),
1694            NativeFunction::from_copy_closure_with_captures(
1695                |_this, args, captures, context| {
1696                    /// Capture object for the abstract `throwReason` closure.
1697                    #[derive(Debug, Trace, Finalize)]
1698                    struct ThrowReasonCaptures {
1699                        reason: JsValue,
1700                    }
1701
1702                    let reason = args.get_or_undefined(0);
1703
1704                    // i. Let result be ? Call(onFinally, undefined).
1705                    let result = captures
1706                        .on_finally
1707                        .call(&JsValue::undefined(), &[], context)?;
1708
1709                    // ii. Let promise be ? PromiseResolve(C, result).
1710                    let promise = Self::promise_resolve(&captures.c, result, context)?;
1711
1712                    // iii. Let throwReason be a new Abstract Closure with no parameters that captures reason and performs the following steps when called:
1713                    let throw_reason = FunctionObjectBuilder::new(
1714                        context.realm(),
1715                        NativeFunction::from_copy_closure_with_captures(
1716                            |_this, _args, captures, _context| {
1717                                // 1. Return ThrowCompletion(reason).
1718                                Err(JsError::from_opaque(captures.reason.clone()))
1719                            },
1720                            ThrowReasonCaptures {
1721                                reason: reason.clone(),
1722                            },
1723                        ),
1724                    );
1725
1726                    // iv. Let thrower be CreateBuiltinFunction(throwReason, 0, "", « »).
1727                    let thrower = throw_reason.length(0).name("").build();
1728
1729                    // v. Return ? Invoke(promise, "then", « thrower »).
1730                    promise.invoke(js_string!("then"), &[thrower.into()], context)
1731                },
1732                FinallyCaptures { on_finally, c },
1733            ),
1734        );
1735
1736        // d. Let catchFinally be CreateBuiltinFunction(catchFinallyClosure, 1, "", « »).
1737        let catch_finally = catch_finally_closure.length(1).name("").build();
1738
1739        (then_finally, catch_finally)
1740    }
1741
1742    /// `Promise.prototype.then ( onFulfilled, onRejected )`
1743    ///
1744    /// More information:
1745    ///  - [ECMAScript reference][spec]
1746    ///  - [MDN documentation][mdn]
1747    ///
1748    /// [spec]: https://tc39.es/ecma262/#sec-promise.prototype.then
1749    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
1750    pub(crate) fn then(
1751        this: &JsValue,
1752        args: &[JsValue],
1753        context: &mut Context,
1754    ) -> JsResult<JsValue> {
1755        // 1. Let promise be the this value.
1756        let promise = this;
1757
1758        // 2. If IsPromise(promise) is false, throw a TypeError exception.
1759        let promise = promise.as_promise_object().ok_or_else(|| {
1760            JsNativeError::typ().with_message("Promise.prototype.then: this is not a promise")
1761        })?;
1762
1763        let on_fulfilled = args
1764            .get_or_undefined(0)
1765            .as_object()
1766            .and_then(JsFunction::from_object);
1767        let on_rejected = args
1768            .get_or_undefined(1)
1769            .as_object()
1770            .and_then(JsFunction::from_object);
1771
1772        // continues in `Promise::inner_then`
1773        Self::inner_then(&promise, on_fulfilled, on_rejected, context).map(JsValue::from)
1774    }
1775
1776    /// Schedules callback functions for the eventual completion of `promise` — either fulfillment
1777    /// or rejection.
1778    pub(crate) fn inner_then(
1779        promise: &JsObject,
1780        on_fulfilled: Option<JsFunction>,
1781        on_rejected: Option<JsFunction>,
1782        context: &mut Context,
1783    ) -> JsResult<JsObject> {
1784        // 3. Let C be ? SpeciesConstructor(promise, %Promise%).
1785        let c = promise.species_constructor(StandardConstructors::promise, context)?;
1786
1787        // 4. Let resultCapability be ? NewPromiseCapability(C).
1788        let result_capability = PromiseCapability::new(&c, context)?;
1789        let result_promise = result_capability.promise.clone();
1790
1791        // 5. Return PerformPromiseThen(promise, onFulfilled, onRejected, resultCapability).
1792        Self::perform_promise_then(
1793            promise,
1794            on_fulfilled,
1795            on_rejected,
1796            Some(result_capability),
1797            context,
1798        );
1799
1800        Ok(result_promise)
1801    }
1802
1803    /// `PerformPromiseThen ( promise, onFulfilled, onRejected [ , resultCapability ] )`
1804    ///
1805    /// More information:
1806    ///  - [ECMAScript reference][spec]
1807    ///
1808    /// [spec]: https://tc39.es/ecma262/#sec-performpromisethen
1809    pub(crate) fn perform_promise_then(
1810        promise: &JsObject,
1811        on_fulfilled: Option<JsFunction>,
1812        on_rejected: Option<JsFunction>,
1813        result_capability: Option<PromiseCapability>,
1814        context: &mut Context,
1815    ) {
1816        // 1. Assert: IsPromise(promise) is true.
1817
1818        // 2. If resultCapability is not present, then
1819        //   a. Set resultCapability to undefined.
1820
1821        // 3. If IsCallable(onFulfilled) is false, then
1822        //   a. Let onFulfilledJobCallback be empty.
1823        // Argument already asserts this.
1824        let on_fulfilled_job_callback = on_fulfilled
1825            // 4. Else,
1826            //   a. Let onFulfilledJobCallback be HostMakeJobCallback(onFulfilled).
1827            .map(|f| context.host_hooks().make_job_callback(f, context));
1828
1829        // 5. If IsCallable(onRejected) is false, then
1830        //   a. Let onRejectedJobCallback be empty.
1831        // Argument already asserts this.
1832        let on_rejected_job_callback = on_rejected
1833            // 6. Else,
1834            //   a. Let onRejectedJobCallback be HostMakeJobCallback(onRejected).
1835            .map(|f| context.host_hooks().make_job_callback(f, context));
1836
1837        // 7. Let fulfillReaction be the PromiseReaction { [[Capability]]: resultCapability, [[Type]]: Fulfill, [[Handler]]: onFulfilledJobCallback }.
1838        let fulfill_reaction = ReactionRecord {
1839            promise_capability: result_capability.clone(),
1840            reaction_type: ReactionType::Fulfill,
1841            handler: on_fulfilled_job_callback,
1842        };
1843
1844        // 8. Let rejectReaction be the PromiseReaction { [[Capability]]: resultCapability, [[Type]]: Reject, [[Handler]]: onRejectedJobCallback }.
1845        let reject_reaction = ReactionRecord {
1846            promise_capability: result_capability,
1847            reaction_type: ReactionType::Reject,
1848            handler: on_rejected_job_callback,
1849        };
1850
1851        let (state, handled) = {
1852            let promise = promise
1853                .downcast_ref::<Self>()
1854                .expect("IsPromise(promise) is false");
1855            (promise.state.clone(), promise.handled)
1856        };
1857
1858        match state {
1859            // 9. If promise.[[PromiseState]] is pending, then
1860            PromiseState::Pending => {
1861                let mut promise = promise
1862                    .downcast_mut::<Self>()
1863                    .expect("IsPromise(promise) is false");
1864                //   a. Append fulfillReaction as the last element of the List that is promise.[[PromiseFulfillReactions]].
1865                promise.fulfill_reactions.push(fulfill_reaction);
1866
1867                //   b. Append rejectReaction as the last element of the List that is promise.[[PromiseRejectReactions]].
1868                promise.reject_reactions.push(reject_reaction);
1869            }
1870
1871            // 10. Else if promise.[[PromiseState]] is fulfilled, then
1872            //   a. Let value be promise.[[PromiseResult]].
1873            PromiseState::Fulfilled(ref value) => {
1874                //   b. Let fulfillJob be NewPromiseReactionJob(fulfillReaction, value).
1875                let fulfill_job =
1876                    new_promise_reaction_job(fulfill_reaction, value.clone(), context);
1877
1878                //   c. Perform HostEnqueuePromiseJob(fulfillJob.[[Job]], fulfillJob.[[Realm]]).
1879                context
1880                    .job_executor()
1881                    .enqueue_job(fulfill_job.into(), context);
1882            }
1883
1884            // 11. Else,
1885            //   a. Assert: The value of promise.[[PromiseState]] is rejected.
1886            //   b. Let reason be promise.[[PromiseResult]].
1887            PromiseState::Rejected(ref reason) => {
1888                //   c. If promise.[[PromiseIsHandled]] is false, perform HostPromiseRejectionTracker(promise, "handle").
1889                if !handled {
1890                    context.host_hooks().promise_rejection_tracker(
1891                        promise,
1892                        OperationType::Handle,
1893                        context,
1894                    );
1895                }
1896
1897                //   d. Let rejectJob be NewPromiseReactionJob(rejectReaction, reason).
1898                let reject_job = new_promise_reaction_job(reject_reaction, reason.clone(), context);
1899
1900                //   e. Perform HostEnqueuePromiseJob(rejectJob.[[Job]], rejectJob.[[Realm]]).
1901                context
1902                    .job_executor()
1903                    .enqueue_job(reject_job.into(), context);
1904
1905                // 12. Set promise.[[PromiseIsHandled]] to true.
1906                promise
1907                    .downcast_mut::<Self>()
1908                    .expect("IsPromise(promise) is false")
1909                    .handled = true;
1910            }
1911        }
1912
1913        // 13. If resultCapability is undefined, then
1914        //   a. Return undefined.
1915        // 14. Else,
1916        //   a. Return resultCapability.[[Promise]].
1917        // skipped because we can already access the promise from `result_capability`
1918    }
1919
1920    /// `GetPromiseResolve ( promiseConstructor )`
1921    ///
1922    /// The abstract operation `GetPromiseResolve` takes argument `promiseConstructor` (a
1923    /// constructor) and returns either a normal completion containing a function object or a throw
1924    /// completion.
1925    ///
1926    /// More information:
1927    ///  - [ECMAScript reference][spec]
1928    ///
1929    /// [spec]: https://tc39.es/ecma262/#sec-getpromiseresolve
1930    pub(crate) fn get_promise_resolve(
1931        promise_constructor: &JsObject,
1932        context: &mut Context,
1933    ) -> JsResult<JsObject> {
1934        // 1. Let promiseResolve be ? Get(promiseConstructor, "resolve").
1935        let promise_resolve = promise_constructor.get(js_string!("resolve"), context)?;
1936
1937        // 2. If IsCallable(promiseResolve) is false, throw a TypeError exception.
1938        promise_resolve.as_callable().ok_or_else(|| {
1939            JsNativeError::typ()
1940                .with_message("retrieving a non-callable promise resolver")
1941                .into()
1942        })
1943    }
1944
1945    /// `CreateResolvingFunctions ( promise )`
1946    ///
1947    /// More information:
1948    ///  - [ECMAScript reference][spec]
1949    ///
1950    /// [spec]: https://tc39.es/ecma262/#sec-createresolvingfunctions
1951    pub(crate) fn create_resolving_functions(
1952        promise: &JsObject,
1953        context: &mut Context,
1954    ) -> ResolvingFunctions {
1955        /// `TriggerPromiseReactions ( reactions, argument )`
1956        ///
1957        /// The abstract operation `TriggerPromiseReactions` takes arguments `reactions` (a `List` of
1958        /// `PromiseReaction` Records) and `argument` and returns unused. It enqueues a new `Job` for
1959        /// each record in `reactions`. Each such `Job` processes the `[[Type]]` and `[[Handler]]` of
1960        /// the `PromiseReaction` Record, and if the `[[Handler]]` is not `empty`, calls it passing the
1961        /// given argument. If the `[[Handler]]` is `empty`, the behaviour is determined by the
1962        /// `[[Type]]`.
1963        ///
1964        /// More information:
1965        ///  - [ECMAScript reference][spec]
1966        ///
1967        /// [spec]: https://tc39.es/ecma262/#sec-triggerpromisereactions
1968        fn trigger_promise_reactions(
1969            reactions: Vec<ReactionRecord>,
1970            argument: &JsValue,
1971            context: &mut Context,
1972        ) {
1973            // 1. For each element reaction of reactions, do
1974            for reaction in reactions {
1975                // a. Let job be NewPromiseReactionJob(reaction, argument).
1976                let job = new_promise_reaction_job(reaction, argument.clone(), context);
1977
1978                // b. Perform HostEnqueuePromiseJob(job.[[Job]], job.[[Realm]]).
1979                context.job_executor().enqueue_job(job.into(), context);
1980            }
1981            // 2. Return unused.
1982        }
1983
1984        /// `FulfillPromise ( promise, value )`
1985        ///
1986        /// The abstract operation `FulfillPromise` takes arguments `promise` and `value` and returns
1987        /// `unused`.
1988        ///
1989        /// More information:
1990        ///  - [ECMAScript reference][spec]
1991        ///
1992        /// [spec]: https://tc39.es/ecma262/#sec-fulfillpromise
1993        ///
1994        /// # Panics
1995        ///
1996        /// Panics if `Promise` is not pending.
1997        fn fulfill_promise(promise: &JsObject, value: JsValue, context: &mut Context) {
1998            let mut promise = promise
1999                .downcast_mut::<Promise>()
2000                .expect("IsPromise(promise) is false");
2001
2002            // 1. Assert: The value of promise.[[PromiseState]] is pending.
2003            assert!(
2004                matches!(promise.state, PromiseState::Pending),
2005                "promise was not pending"
2006            );
2007
2008            // reordering these statements does not affect the semantics
2009
2010            // 2. Let reactions be promise.[[PromiseFulfillReactions]].
2011            // 4. Set promise.[[PromiseFulfillReactions]] to undefined.
2012            let reactions = std::mem::take(&mut promise.fulfill_reactions);
2013
2014            // 5. Set promise.[[PromiseRejectReactions]] to undefined.
2015            promise.reject_reactions.clear();
2016
2017            // 7. Perform TriggerPromiseReactions(reactions, value).
2018            trigger_promise_reactions(reactions, &value, context);
2019
2020            // 3. Set promise.[[PromiseResult]] to value.
2021            // 6. Set promise.[[PromiseState]] to fulfilled.
2022            promise.state = PromiseState::Fulfilled(value);
2023
2024            // 8. Return unused.
2025        }
2026
2027        /// `RejectPromise ( promise, reason )`
2028        ///
2029        /// The abstract operation `RejectPromise` takes arguments `promise` and `reason` and returns
2030        /// `unused`.
2031        ///
2032        /// More information:
2033        ///  - [ECMAScript reference][spec]
2034        ///
2035        /// [spec]: https://tc39.es/ecma262/#sec-rejectpromise
2036        ///
2037        /// # Panics
2038        ///
2039        /// Panics if `Promise` is not pending.
2040        fn reject_promise(promise: &JsObject, reason: JsValue, context: &mut Context) {
2041            let handled = {
2042                let mut promise = promise
2043                    .downcast_mut::<Promise>()
2044                    .expect("IsPromise(promise) is false");
2045
2046                // 1. Assert: The value of promise.[[PromiseState]] is pending.
2047                assert!(
2048                    matches!(promise.state, PromiseState::Pending),
2049                    "Expected promise.[[PromiseState]] to be pending"
2050                );
2051
2052                // reordering these statements does not affect the semantics
2053
2054                // 2. Let reactions be promise.[[PromiseRejectReactions]].
2055                // 5. Set promise.[[PromiseRejectReactions]] to undefined.
2056                let reactions = std::mem::take(&mut promise.reject_reactions);
2057
2058                // 4. Set promise.[[PromiseFulfillReactions]] to undefined.
2059                promise.fulfill_reactions.clear();
2060
2061                // 8. Perform TriggerPromiseReactions(reactions, reason).
2062                trigger_promise_reactions(reactions, &reason, context);
2063
2064                // 3. Set promise.[[PromiseResult]] to reason.
2065                // 6. Set promise.[[PromiseState]] to rejected.
2066                promise.state = PromiseState::Rejected(reason);
2067
2068                promise.handled
2069            };
2070
2071            // 7. If promise.[[PromiseIsHandled]] is false, perform HostPromiseRejectionTracker(promise, "reject").
2072            if !handled {
2073                context.host_hooks().promise_rejection_tracker(
2074                    promise,
2075                    OperationType::Reject,
2076                    context,
2077                );
2078            }
2079
2080            // 9. Return unused.
2081        }
2082
2083        // 1. Let alreadyResolved be the Record { [[Value]]: false }.
2084        // 5. Set resolve.[[Promise]] to promise.
2085        // 6. Set resolve.[[AlreadyResolved]] to alreadyResolved.
2086        let promise = Gc::new(Cell::new(Some(promise.clone())));
2087
2088        // 2. Let stepsResolve be the algorithm steps defined in Promise Resolve Functions.
2089        // 3. Let lengthResolve be the number of non-optional parameters of the function definition in Promise Resolve Functions.
2090        // 4. Let resolve be CreateBuiltinFunction(stepsResolve, lengthResolve, "", « [[Promise]], [[AlreadyResolved]] »).
2091        let resolve = FunctionObjectBuilder::new(
2092            context.realm(),
2093            NativeFunction::from_copy_closure_with_captures(
2094                |_this, args, captures, context| {
2095                    // https://tc39.es/ecma262/#sec-promise-resolve-functions
2096
2097                    // 1. Let F be the active function object.
2098                    // 2. Assert: F has a [[Promise]] internal slot whose value is an Object.
2099                    // 3. Let promise be F.[[Promise]].
2100                    // 4. Let alreadyResolved be F.[[AlreadyResolved]].
2101                    // 5. If alreadyResolved.[[Value]] is true, return undefined.
2102                    // 6. Set alreadyResolved.[[Value]] to true.
2103                    let Some(promise) = captures.take() else {
2104                        return Ok(JsValue::undefined());
2105                    };
2106
2107                    let resolution = args.get_or_undefined(0);
2108
2109                    // 7. If SameValue(resolution, promise) is true, then
2110                    if JsValue::same_value(resolution, &promise.clone().into()) {
2111                        //   a. Let selfResolutionError be a newly created TypeError object.
2112                        let self_resolution_error = JsNativeError::typ()
2113                            .with_message("SameValue(resolution, promise) is true")
2114                            .to_opaque(context);
2115
2116                        //   b. Perform RejectPromise(promise, selfResolutionError).
2117                        reject_promise(&promise, self_resolution_error.into(), context);
2118
2119                        //   c. Return undefined.
2120                        return Ok(JsValue::undefined());
2121                    }
2122
2123                    let Some(then) = resolution.as_object() else {
2124                        // 8. If Type(resolution) is not Object, then
2125                        //   a. Perform FulfillPromise(promise, resolution).
2126                        fulfill_promise(&promise, resolution.clone(), context);
2127
2128                        //   b. Return undefined.
2129                        return Ok(JsValue::undefined());
2130                    };
2131
2132                    // 9. Let then be Completion(Get(resolution, "then")).
2133                    let then_action = match then.get(js_string!("then"), context) {
2134                        // 10. If then is an abrupt completion, then
2135                        Err(e) => {
2136                            //   a. Perform RejectPromise(promise, then.[[Value]]).
2137                            reject_promise(&promise, e.to_opaque(context), context);
2138
2139                            //   b. Return undefined.
2140                            return Ok(JsValue::undefined());
2141                        }
2142                        // 11. Let thenAction be then.[[Value]].
2143                        Ok(then) => then,
2144                    };
2145
2146                    // 12. If IsCallable(thenAction) is false, then
2147                    let Some(then_action) =
2148                        then_action.as_object().and_then(JsFunction::from_object)
2149                    else {
2150                        // a. Perform FulfillPromise(promise, resolution).
2151                        fulfill_promise(&promise, resolution.clone(), context);
2152
2153                        //   b. Return undefined.
2154                        return Ok(JsValue::undefined());
2155                    };
2156
2157                    // 13. Let thenJobCallback be HostMakeJobCallback(thenAction).
2158                    let then_job_callback =
2159                        context.host_hooks().make_job_callback(then_action, context);
2160
2161                    // 14. Let job be NewPromiseResolveThenableJob(promise, resolution, thenJobCallback).
2162                    let job = new_promise_resolve_thenable_job(
2163                        promise.clone(),
2164                        resolution.clone(),
2165                        then_job_callback,
2166                        context,
2167                    );
2168
2169                    // 15. Perform HostEnqueuePromiseJob(job.[[Job]], job.[[Realm]]).
2170                    context.job_executor().enqueue_job(job.into(), context);
2171
2172                    // 16. Return undefined.
2173                    Ok(JsValue::undefined())
2174                },
2175                promise.clone(),
2176            ),
2177        )
2178        .name("")
2179        .length(1)
2180        .constructor(false)
2181        .build();
2182
2183        // 10. Set reject.[[Promise]] to promise.
2184        // 11. Set reject.[[AlreadyResolved]] to alreadyResolved.
2185        // 7. Let stepsReject be the algorithm steps defined in Promise Reject Functions.
2186        // 8. Let lengthReject be the number of non-optional parameters of the function definition in Promise Reject Functions.
2187        // 9. Let reject be CreateBuiltinFunction(stepsReject, lengthReject, "", « [[Promise]], [[AlreadyResolved]] »).
2188        let reject = FunctionObjectBuilder::new(
2189            context.realm(),
2190            NativeFunction::from_copy_closure_with_captures(
2191                |_this, args, captures, context| {
2192                    // https://tc39.es/ecma262/#sec-promise-reject-functions
2193
2194                    // 1. Let F be the active function object.
2195                    // 2. Assert: F has a [[Promise]] internal slot whose value is an Object.
2196                    // 3. Let promise be F.[[Promise]].
2197                    // 4. Let alreadyResolved be F.[[AlreadyResolved]].
2198                    // 5. If alreadyResolved.[[Value]] is true, return undefined.
2199                    // 6. Set alreadyResolved.[[Value]] to true.
2200                    let Some(promise) = captures.take() else {
2201                        return Ok(JsValue::undefined());
2202                    };
2203
2204                    // 7. Perform RejectPromise(promise, reason).
2205                    reject_promise(&promise, args.get_or_undefined(0).clone(), context);
2206
2207                    // 8. Return undefined.
2208                    Ok(JsValue::undefined())
2209                },
2210                promise,
2211            ),
2212        )
2213        .name("")
2214        .length(1)
2215        .constructor(false)
2216        .build();
2217
2218        // 12. Return the Record { [[Resolve]]: resolve, [[Reject]]: reject }.
2219        ResolvingFunctions { resolve, reject }
2220    }
2221}
2222
2223/// More information:
2224///  - [ECMAScript reference][spec]
2225///
2226/// [spec]: https://tc39.es/ecma262/#sec-newpromisereactionjob
2227fn new_promise_reaction_job(
2228    mut reaction: ReactionRecord,
2229    argument: JsValue,
2230    context: &mut Context,
2231) -> PromiseJob {
2232    // Inverting order since `job` captures `reaction` by value.
2233
2234    // 2. Let handlerRealm be null.
2235    // 3. If reaction.[[Handler]] is not empty, then
2236    //   a. Let getHandlerRealmResult be Completion(GetFunctionRealm(reaction.[[Handler]].[[Callback]])).
2237    //   b. If getHandlerRealmResult is a normal completion, set handlerRealm to getHandlerRealmResult.[[Value]].
2238    //   c. Else, set handlerRealm to the current Realm Record.
2239    //   d. NOTE: handlerRealm is never null unless the handler is undefined. When the handler is a
2240    // revoked Proxy and no ECMAScript code runs, handlerRealm is used to create error objects.
2241    let realm = reaction
2242        .handler
2243        .as_ref()
2244        .and_then(|handler| handler.callback().get_function_realm(context).ok())
2245        .unwrap_or_else(|| context.realm().clone());
2246
2247    // 1. Let job be a new Job Abstract Closure with no parameters that captures reaction and argument and performs the following steps when called:
2248    let job = move |context: &mut Context| {
2249        //   a. Let promiseCapability be reaction.[[Capability]].
2250        let promise_capability = reaction.promise_capability.take();
2251        //   b. Let type be reaction.[[Type]].
2252        let reaction_type = reaction.reaction_type;
2253        //   c. Let handler be reaction.[[Handler]].
2254        let handler = reaction.handler.take();
2255
2256        let handler_result = match handler {
2257            // d. If handler is empty, then
2258            None => match reaction_type {
2259                // i. If type is Fulfill, let handlerResult be NormalCompletion(argument).
2260                ReactionType::Fulfill => Ok(argument.clone()),
2261                // ii. Else,
2262                //   1. Assert: type is Reject.
2263                ReactionType::Reject => {
2264                    // 2. Let handlerResult be ThrowCompletion(argument).
2265                    Err(argument.clone())
2266                }
2267            },
2268            //   e. Else, let handlerResult be Completion(HostCallJobCallback(handler, undefined, « argument »)).
2269            Some(handler) => context
2270                .host_hooks()
2271                .call_job_callback(
2272                    handler,
2273                    &JsValue::undefined(),
2274                    std::slice::from_ref(&argument),
2275                    context,
2276                )
2277                .map_err(|e| e.to_opaque(context)),
2278        };
2279
2280        match promise_capability {
2281            None => {
2282                // f. If promiseCapability is undefined, then
2283                //    i. Assert: handlerResult is not an abrupt completion.
2284                assert!(
2285                    handler_result.is_ok(),
2286                    "Assertion: <handlerResult is not an abrupt completion> failed"
2287                );
2288
2289                // ii. Return empty.
2290                Ok(JsValue::undefined())
2291            }
2292            Some(promise_capability_record) => {
2293                // g. Assert: promiseCapability is a PromiseCapability Record.
2294                let PromiseCapability {
2295                    promise: _,
2296                    functions: ResolvingFunctions { resolve, reject },
2297                } = &promise_capability_record;
2298
2299                match handler_result {
2300                    // h. If handlerResult is an abrupt completion, then
2301                    Err(value) => {
2302                        // i. Return ? Call(promiseCapability.[[Reject]], undefined, « handlerResult.[[Value]] »).
2303                        reject.call(&JsValue::undefined(), &[value], context)
2304                    }
2305
2306                    // i. Else,
2307                    Ok(value) => {
2308                        // i. Return ? Call(promiseCapability.[[Resolve]], undefined, « handlerResult.[[Value]] »).
2309                        resolve.call(&JsValue::undefined(), &[value], context)
2310                    }
2311                }
2312            }
2313        }
2314    };
2315
2316    // 4. Return the Record { [[Job]]: job, [[Realm]]: handlerRealm }.
2317    PromiseJob::with_realm(job, realm)
2318}
2319
2320/// More information:
2321///  - [ECMAScript reference][spec]
2322///
2323/// [spec]: https://tc39.es/ecma262/#sec-newpromiseresolvethenablejob
2324fn new_promise_resolve_thenable_job(
2325    promise_to_resolve: JsObject,
2326    thenable: JsValue,
2327    then: JobCallback,
2328    context: &mut Context,
2329) -> PromiseJob {
2330    // Inverting order since `job` captures variables by value.
2331
2332    // 2. Let getThenRealmResult be Completion(GetFunctionRealm(then.[[Callback]])).
2333    // 3. If getThenRealmResult is a normal completion, let thenRealm be getThenRealmResult.[[Value]].
2334    // 4. Else, let thenRealm be the current Realm Record.
2335    // 5. NOTE: thenRealm is never null. When then.[[Callback]] is a revoked Proxy and no code runs, thenRealm is used to create error objects.
2336    let realm = then
2337        .callback()
2338        .get_function_realm(context)
2339        .unwrap_or_else(|_| context.realm().clone());
2340
2341    // 1. Let job be a new Job Abstract Closure with no parameters that captures promiseToResolve, thenable, and then and performs the following steps when called:
2342    let job = move |context: &mut Context| {
2343        //    a. Let resolvingFunctions be CreateResolvingFunctions(promiseToResolve).
2344        let resolving_functions = Promise::create_resolving_functions(&promise_to_resolve, context);
2345
2346        //    b. Let thenCallResult be Completion(HostCallJobCallback(then, thenable, « resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]] »)).
2347        let then_call_result = context.host_hooks().call_job_callback(
2348            then,
2349            &thenable,
2350            &[
2351                resolving_functions.resolve.clone().into(),
2352                resolving_functions.reject.clone().into(),
2353            ],
2354            context,
2355        );
2356
2357        //    c. If thenCallResult is an abrupt completion, then
2358        if let Err(value) = then_call_result {
2359            let value = value.to_opaque(context);
2360            //    i. Return ? Call(resolvingFunctions.[[Reject]], undefined, « thenCallResult.[[Value]] »).
2361            return resolving_functions
2362                .reject
2363                .call(&JsValue::undefined(), &[value], context);
2364        }
2365
2366        //    d. Return ? thenCallResult.
2367        then_call_result
2368    };
2369
2370    // 6. Return the Record { [[Job]]: job, [[Realm]]: thenRealm }.
2371    PromiseJob::with_realm(job, realm)
2372}