Skip to main content

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