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