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