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