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