Skip to main content

boa_engine/object/builtins/
jspromise.rs

1//! A Rust API wrapper for Boa's promise Builtin ECMAScript Object
2
3use super::{JsArray, JsFunction};
4use crate::value::TryIntoJs;
5use crate::{
6    Context, JsArgs, JsError, JsExpect, JsNativeError, JsResult, JsValue, NativeFunction,
7    builtins::{
8        Promise,
9        promise::{PromiseState, ResolvingFunctions},
10    },
11    job::NativeAsyncJob,
12    object::JsObject,
13    value::TryFromJs,
14};
15use boa_gc::{Finalize, Gc, GcRefCell, Trace};
16use std::cell::RefCell;
17use std::{future::Future, pin::Pin, task};
18
19/// An ECMAScript [promise] object.
20///
21/// Known as the concurrency primitive of ECMAScript, this is the main struct used to manipulate,
22/// chain and inspect `Promises` from Rust code.
23///
24/// # Examples
25///
26/// ```
27/// # use boa_engine::{
28/// #     builtins::promise::PromiseState,
29/// #     js_string,
30/// #     object::{builtins::JsPromise, FunctionObjectBuilder},
31/// #     property::Attribute,
32/// #     Context, JsArgs, JsError, JsValue, NativeFunction,
33/// # };
34/// # use std::error::Error;
35/// # fn main() -> Result<(), Box<dyn Error>> {
36/// let context = &mut Context::default();
37///
38/// context.register_global_property(
39///     js_string!("finally"),
40///     false,
41///     Attribute::all(),
42/// );
43///
44/// let promise = JsPromise::new(
45///     |resolvers, context| {
46///         let result = js_string!("hello world!").into();
47///         resolvers.resolve.call(
48///             &JsValue::undefined(),
49///             &[result],
50///             context,
51///         )?;
52///         Ok(JsValue::undefined())
53///     },
54///     context,
55/// )?;
56///
57/// let promise = promise
58///     .then(
59///         Some(
60///             NativeFunction::from_fn_ptr(|_, args, _| {
61///                 Err(JsError::from_opaque(args.get_or_undefined(0).clone())
62///                     .into())
63///             })
64///             .to_js_function(context.realm()),
65///         ),
66///         None,
67///         context,
68///     )?
69///     .catch(
70///         NativeFunction::from_fn_ptr(|_, args, _| {
71///             Ok(args.get_or_undefined(0).clone())
72///         })
73///         .to_js_function(context.realm()),
74///         context,
75///     )?
76///     .finally(
77///         NativeFunction::from_fn_ptr(|_, _, context| {
78///             context.global_object().clone().set(
79///                 js_string!("finally"),
80///                 JsValue::from(true),
81///                 true,
82///                 context,
83///             )?;
84///             Ok(JsValue::undefined())
85///         })
86///         .to_js_function(context.realm()),
87///         context,
88///     )?;
89///
90/// context.run_jobs();
91///
92/// assert_eq!(
93///     promise.state(),
94///     PromiseState::Fulfilled(js_string!("hello world!").into())
95/// );
96///
97/// assert_eq!(
98///     context
99///         .global_object()
100///         .clone()
101///         .get(js_string!("finally"), context)?,
102///     JsValue::from(true)
103/// );
104///
105/// # Ok(())
106/// # }
107/// ```
108///
109/// [promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
110#[derive(Debug, Clone, Trace, Finalize)]
111pub struct JsPromise {
112    inner: JsObject<Promise>,
113}
114
115impl JsPromise {
116    /// Creates a new promise object from an executor function.
117    ///
118    /// It is equivalent to calling the [`Promise()`] constructor, which makes it share the same
119    /// execution semantics as the constructor:
120    /// - The executor function `executor` is called synchronously just after the promise is created.
121    /// - The executor return value is ignored.
122    /// - Any error thrown within the execution of `executor` will call the `reject` function
123    ///   of the newly created promise, unless either `resolve` or `reject` were already called
124    ///   beforehand.
125    ///
126    /// `executor` receives as an argument the [`ResolvingFunctions`] needed to settle the promise,
127    /// which can be done by either calling the `resolve` function or the `reject` function.
128    ///
129    /// # Examples
130    ///
131    /// ```
132    /// # use std::error::Error;
133    /// # use boa_engine::{
134    /// #    object::builtins::JsPromise,
135    /// #    builtins::promise::PromiseState,
136    /// #    Context, JsValue, js_string
137    /// # };
138    /// # fn main() -> Result<(), Box<dyn Error>> {
139    /// let context = &mut Context::default();
140    ///
141    /// let promise = JsPromise::new(
142    ///     |resolvers, context| {
143    ///         let result = js_string!("hello world").into();
144    ///         resolvers.resolve.call(
145    ///             &JsValue::undefined(),
146    ///             &[result],
147    ///             context,
148    ///         )?;
149    ///         Ok(JsValue::undefined())
150    ///     },
151    ///     context,
152    /// )?;
153    ///
154    /// context.run_jobs();
155    ///
156    /// assert_eq!(
157    ///     promise.state(),
158    ///     PromiseState::Fulfilled(js_string!("hello world").into())
159    /// );
160    /// # Ok(())
161    /// # }
162    /// ```
163    ///
164    /// [`Promise()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise
165    pub fn new<F>(executor: F, context: &mut Context) -> JsResult<Self>
166    where
167        F: FnOnce(&ResolvingFunctions, &mut Context) -> JsResult<JsValue>,
168    {
169        let promise = JsObject::from_proto_and_data_with_shared_shape(
170            context.root_shape(),
171            context.intrinsics().constructors().promise().prototype(),
172            Promise::new(),
173        );
174        let resolvers = Promise::create_resolving_functions(&promise, context);
175
176        if let Err(e) = executor(&resolvers, context) {
177            let e = e.into_opaque(context)?;
178            resolvers
179                .reject
180                .call(&JsValue::undefined(), &[e], context)
181                .js_expect("default `reject` function cannot throw")?;
182        }
183
184        Ok(Self { inner: promise })
185    }
186
187    /// Creates a new pending promise and returns it and its associated `ResolvingFunctions`.
188    ///
189    /// This can be useful when you want to manually settle a promise from Rust code, instead of
190    /// running an `executor` function that automatically settles the promise on creation
191    /// (see [`JsPromise::new`]).
192    ///
193    /// # Examples
194    ///
195    /// ```
196    /// # use std::error::Error;
197    /// # use boa_engine::{
198    /// #    object::builtins::JsPromise,
199    /// #    builtins::promise::PromiseState,
200    /// #    Context, JsValue
201    /// # };
202    /// # fn main() -> Result<(), Box<dyn Error>> {
203    /// let context = &mut Context::default();
204    ///
205    /// let (promise, resolvers) = JsPromise::new_pending(context);
206    ///
207    /// assert_eq!(promise.state(), PromiseState::Pending);
208    ///
209    /// resolvers
210    ///     .reject
211    ///     .call(&JsValue::undefined(), &[5.into()], context)?;
212    ///
213    /// assert_eq!(promise.state(), PromiseState::Rejected(5.into()));
214    ///
215    /// # Ok(())
216    /// # }
217    /// ```
218    #[inline]
219    pub fn new_pending(context: &mut Context) -> (Self, ResolvingFunctions) {
220        let promise = JsObject::from_proto_and_data_with_shared_shape(
221            context.root_shape(),
222            context.intrinsics().constructors().promise().prototype(),
223            Promise::new(),
224        );
225        let resolvers = Promise::create_resolving_functions(&promise, context);
226
227        (promise.into(), resolvers)
228    }
229
230    /// Wraps an existing object with the `JsPromise` interface, returning `Err` if the object
231    /// is not a valid promise.
232    ///
233    /// # Examples
234    ///
235    /// ```
236    /// # use std::error::Error;
237    /// # use boa_engine::{
238    /// #    object::builtins::JsPromise,
239    /// #    builtins::promise::PromiseState,
240    /// #    Context, JsObject, JsValue, Source
241    /// # };
242    /// # fn main() -> Result<(), Box<dyn Error>> {
243    /// let context = &mut Context::default();
244    ///
245    /// let promise = context.eval(Source::from_bytes(
246    ///     "new Promise((resolve, reject) => resolve())",
247    /// ))?;
248    /// let promise = promise.as_object().unwrap();
249    ///
250    /// let promise = JsPromise::from_object(promise)?;
251    ///
252    /// assert_eq!(
253    ///     promise.state(),
254    ///     PromiseState::Fulfilled(JsValue::undefined())
255    /// );
256    ///
257    /// assert!(JsPromise::from_object(JsObject::with_null_proto()).is_err());
258    ///
259    /// # Ok(())
260    /// # }
261    /// ```
262    #[inline]
263    pub fn from_object(object: JsObject) -> JsResult<Self> {
264        let Ok(inner) = object.downcast::<Promise>() else {
265            return Err(JsNativeError::typ()
266                .with_message("`object` is not a Promise")
267                .into());
268        };
269        Ok(Self { inner })
270    }
271
272    /// Creates a new `JsPromise` from a [`Future`]-like.
273    ///
274    /// If you want to convert a Rust async function into an ECMAScript async function, see
275    /// [`NativeFunction::from_async_fn`][async_fn].
276    ///
277    /// # Examples
278    ///
279    /// ```
280    /// use std::error::Error;
281    /// # use std::cell::RefCell;
282    /// # use boa_engine::{
283    /// #    object::builtins::JsPromise,
284    /// #    builtins::promise::PromiseState,
285    /// #    Context, JsResult, JsValue
286    /// # };
287    /// async fn f(_: &RefCell<&mut Context>) -> JsResult<JsValue> {
288    ///     Ok(JsValue::null())
289    /// }
290    /// let context = &mut Context::default();
291    ///
292    /// let promise = JsPromise::from_async_fn(f, context);
293    ///
294    /// context.run_jobs();
295    ///
296    /// assert_eq!(promise.state(), PromiseState::Fulfilled(JsValue::null()));
297    /// ```
298    ///
299    /// [async_fn]: crate::native_function::NativeFunction::from_async_fn
300    pub fn from_async_fn<F>(f: F, context: &mut Context) -> Self
301    where
302        F: AsyncFnOnce(&RefCell<&mut Context>) -> JsResult<JsValue> + 'static,
303    {
304        let (promise, resolvers) = Self::new_pending(context);
305
306        context.enqueue_job(
307            NativeAsyncJob::new(async move |context| {
308                let result = f(context).await;
309
310                let context = &mut context.borrow_mut();
311                match result {
312                    Ok(v) => resolvers.resolve.call(&JsValue::undefined(), &[v], context),
313                    Err(e) => {
314                        let e = e.into_opaque(context)?;
315                        resolvers.reject.call(&JsValue::undefined(), &[e], context)
316                    }
317                }
318            })
319            .into(),
320        );
321
322        promise
323    }
324
325    /// Creates a new `JsPromise` from a `Result<T, JsError>`, where `T` is the fulfilled value of
326    /// the promise, and `JsError` is the rejection reason. This is a simpler way to create a
327    /// promise that is either fulfilled or rejected based on the result of a computation.
328    ///
329    /// # Examples
330    ///
331    /// ```
332    /// # use std::error::Error;
333    /// # use boa_engine::{
334    /// #    object::builtins::JsPromise,
335    /// #    builtins::promise::PromiseState,
336    /// #    Context, JsResult, JsString, js_string, js_error
337    /// # };
338    /// # fn main() -> Result<(), Box<dyn Error>> {
339    /// let context = &mut Context::default();
340    ///
341    /// fn do_thing(success: bool) -> JsResult<JsString> {
342    ///     success
343    ///         .then(|| js_string!("resolved!"))
344    ///         .ok_or(js_error!("rejected!"))
345    /// }
346    ///
347    /// let promise = JsPromise::from_result(do_thing(true), context)?;
348    /// assert_eq!(
349    ///     promise.state(),
350    ///     PromiseState::Fulfilled(js_string!("resolved!").into())
351    /// );
352    ///
353    /// let promise = JsPromise::from_result(do_thing(false), context)?;
354    /// assert_eq!(
355    ///     promise.state(),
356    ///     PromiseState::Rejected(js_string!("rejected!").into())
357    /// );
358    /// # Ok(())
359    /// # }
360    /// ```
361    pub fn from_result<V: Into<JsValue>, E: Into<JsError>>(
362        value: Result<V, E>,
363        context: &mut Context,
364    ) -> JsResult<Self> {
365        match value {
366            Ok(v) => Self::resolve(v, context),
367            Err(e) => Self::reject(e, context),
368        }
369    }
370
371    /// Resolves a `JsValue` into a `JsPromise`.
372    ///
373    /// Equivalent to the [`Promise.resolve()`] static method.
374    ///
375    /// This function is mainly used to wrap a plain `JsValue` into a fulfilled promise, but it can
376    /// also flatten nested layers of [thenables], which essentially converts them into native
377    /// promises.
378    ///
379    /// # Examples
380    ///
381    /// ```
382    /// # use std::error::Error;
383    /// # use boa_engine::{
384    /// #    object::builtins::JsPromise,
385    /// #    builtins::promise::PromiseState,
386    /// #    Context, js_string
387    /// # };
388    /// # fn main() -> Result<(), Box<dyn Error>> {
389    /// let context = &mut Context::default();
390    ///
391    /// let promise = JsPromise::resolve(js_string!("resolved!"), context)?;
392    ///
393    /// assert_eq!(
394    ///     promise.state(),
395    ///     PromiseState::Fulfilled(js_string!("resolved!").into())
396    /// );
397    /// # Ok(())
398    /// # }
399    /// ```
400    ///
401    /// [`Promise.resolve()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve
402    /// [thenables]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#thenables
403    pub fn resolve<V: Into<JsValue>>(value: V, context: &mut Context) -> JsResult<Self> {
404        Promise::promise_resolve(
405            &context.intrinsics().constructors().promise().constructor(),
406            value.into(),
407            context,
408        )
409        .and_then(Self::from_object)
410        .js_expect("default resolve functions cannot throw and must return a promise")
411        .map_err(Into::into)
412    }
413
414    /// Creates a `JsPromise` that is rejected with the reason `error`.
415    ///
416    /// Equivalent to the [`Promise.reject`] static method.
417    ///
418    /// `JsPromise::reject` is pretty similar to [`JsPromise::resolve`], with the difference that
419    /// it always wraps `error` into a rejected promise, even if `error` is a promise or a [thenable].
420    ///
421    /// # Examples
422    ///
423    /// ```
424    /// # use std::error::Error;
425    /// # use boa_engine::{
426    /// #    object::builtins::JsPromise,
427    /// #    builtins::promise::PromiseState,
428    /// #    Context, js_string, JsError
429    /// # };
430    /// # fn main() -> Result<(), Box<dyn Error>> {
431    /// let context = &mut Context::default();
432    ///
433    /// let promise = JsPromise::reject(
434    ///     JsError::from_opaque(js_string!("oops!").into()),
435    ///     context,
436    /// )?;
437    ///
438    /// assert_eq!(
439    ///     promise.state(),
440    ///     PromiseState::Rejected(js_string!("oops!").into())
441    /// );
442    /// # Ok(())
443    /// # }
444    /// ```
445    ///
446    /// [`Promise.reject`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/reject
447    /// [thenable]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#thenables
448    pub fn reject<E: Into<JsError>>(error: E, context: &mut Context) -> JsResult<Self> {
449        let error = error.into();
450        assert!(
451            error.is_catchable(),
452            "cannot create a reject function from an uncatchable error"
453        );
454
455        Promise::promise_reject(
456            &context.intrinsics().constructors().promise().constructor(),
457            error,
458            context,
459        )
460        .and_then(Self::from_object)
461        .js_expect("default resolve functions cannot throw and must return a promise")
462        .map_err(Into::into)
463    }
464
465    /// Gets the current state of the promise.
466    ///
467    /// # Examples
468    ///
469    /// ```
470    /// # use std::error::Error;
471    /// # use boa_engine::{
472    /// #    object::builtins::JsPromise,
473    /// #    builtins::promise::PromiseState,
474    /// #    Context
475    /// # };
476    /// let context = &mut Context::default();
477    ///
478    /// let promise = JsPromise::new_pending(context).0;
479    ///
480    /// assert_eq!(promise.state(), PromiseState::Pending);
481    /// ```
482    #[inline]
483    #[must_use]
484    pub fn state(&self) -> PromiseState {
485        self.inner.borrow().data().state().clone()
486    }
487
488    /// Schedules callback functions to run when the promise settles.
489    ///
490    /// Equivalent to the [`Promise.prototype.then`] method.
491    ///
492    /// The return value is a promise that is always pending on return, regardless of the current
493    /// state of the original promise. Two handlers can be provided as callbacks to be executed when
494    /// the original promise settles:
495    ///
496    /// - If the original promise is fulfilled, `on_fulfilled` is called with the fulfillment value
497    ///   of the original promise.
498    /// - If the original promise is rejected, `on_rejected` is called with the rejection reason
499    ///   of the original promise.
500    ///
501    /// The return value of the handlers can be used to mutate the state of the created promise. If
502    /// the callback:
503    ///
504    /// - returns a value: the created promise gets fulfilled with the returned value.
505    /// - doesn't return: the created promise gets fulfilled with undefined.
506    /// - throws: the created promise gets rejected with the thrown error as its value.
507    /// - returns a fulfilled promise: the created promise gets fulfilled with that promise's value as its value.
508    /// - returns a rejected promise: the created promise gets rejected with that promise's value as its value.
509    /// - returns another pending promise: the created promise remains pending but becomes settled with that
510    ///   promise's value as its value immediately after that promise becomes settled.
511    ///
512    /// # Examples
513    ///
514    /// ```
515    /// # use std::error::Error;
516    /// # use boa_engine::{
517    /// #     builtins::promise::PromiseState,
518    /// #     js_string,
519    /// #     object::{builtins::JsPromise, FunctionObjectBuilder},
520    /// #     Context, JsArgs, JsError, JsValue, NativeFunction,
521    /// # };
522    /// # fn main() -> Result<(), Box<dyn Error>> {
523    /// let context = &mut Context::default();
524    ///
525    /// let promise = JsPromise::new(
526    ///     |resolvers, context| {
527    ///         resolvers.resolve.call(
528    ///             &JsValue::undefined(),
529    ///             &[255.255.into()],
530    ///             context,
531    ///         )?;
532    ///         Ok(JsValue::undefined())
533    ///     },
534    ///     context,
535    /// )?
536    /// .then(
537    ///     Some(
538    ///         NativeFunction::from_fn_ptr(|_, args, context| {
539    ///             args.get_or_undefined(0)
540    ///                 .to_string(context)
541    ///                 .map(JsValue::from)
542    ///         })
543    ///         .to_js_function(context.realm()),
544    ///     ),
545    ///     None,
546    ///     context,
547    /// )?;
548    ///
549    /// context.run_jobs();
550    ///
551    /// assert_eq!(
552    ///     promise.state(),
553    ///     PromiseState::Fulfilled(js_string!("255.255").into())
554    /// );
555    /// # Ok(())
556    /// # }
557    /// ```
558    ///
559    /// [`Promise.prototype.then`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
560    #[inline]
561    #[allow(clippy::return_self_not_must_use)] // Could just be used to add handlers on an existing promise
562    pub fn then(
563        &self,
564        on_fulfilled: Option<JsFunction>,
565        on_rejected: Option<JsFunction>,
566        context: &mut Context,
567    ) -> JsResult<Self> {
568        Promise::inner_then(self, on_fulfilled, on_rejected, context)
569            .and_then(Self::from_object)
570            .js_expect("`inner_then` cannot fail for native `JsPromise`")
571            .map_err(Into::into)
572    }
573
574    /// Schedules a callback to run when the promise is rejected.
575    ///
576    /// Equivalent to the [`Promise.prototype.catch`] method.
577    ///
578    /// This is essentially a shortcut for calling [`promise.then(None, Some(function))`][then], which
579    /// only handles the error case and leaves the fulfilled case untouched.
580    ///
581    /// # Examples
582    ///
583    /// ```
584    /// # use std::error::Error;
585    /// # use boa_engine::{
586    /// #     js_string,
587    /// #     builtins::promise::PromiseState,
588    /// #     object::{builtins::JsPromise, FunctionObjectBuilder},
589    /// #     Context, JsArgs, JsNativeError, JsValue, NativeFunction,
590    /// # };
591    /// # fn main() -> Result<(), Box<dyn Error>> {
592    /// let context = &mut Context::default();
593    ///
594    /// let promise = JsPromise::new(
595    ///     |resolvers, context| {
596    ///         let error = JsNativeError::typ().with_message("thrown");
597    ///         let error = error.into_opaque(context);
598    ///         resolvers.reject.call(
599    ///             &JsValue::undefined(),
600    ///             &[error.into()],
601    ///             context,
602    ///         )?;
603    ///         Ok(JsValue::undefined())
604    ///     },
605    ///     context,
606    /// )?
607    /// .catch(
608    ///     NativeFunction::from_fn_ptr(|_, args, context| {
609    ///         args.get_or_undefined(0)
610    ///             .to_string(context)
611    ///             .map(JsValue::from)
612    ///     })
613    ///     .to_js_function(context.realm()),
614    ///     context,
615    /// )?;
616    ///
617    /// context.run_jobs();
618    ///
619    /// assert_eq!(
620    ///     promise.state(),
621    ///     PromiseState::Fulfilled(js_string!("TypeError: thrown").into())
622    /// );
623    /// # Ok(())
624    /// # }
625    /// ```
626    ///
627    /// [`Promise.prototype.catch`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch
628    /// [then]: JsPromise::then
629    #[inline]
630    #[allow(clippy::return_self_not_must_use)] // Could just be used to add a handler on an existing promise
631    pub fn catch(&self, on_rejected: JsFunction, context: &mut Context) -> JsResult<Self> {
632        self.then(None, Some(on_rejected), context)
633    }
634
635    /// Schedules a callback to run when the promise is rejected.
636    ///
637    /// Equivalent to the [`Promise.prototype.finally()`] method.
638    ///
639    /// While this could be seen as a shortcut for calling [`promise.then(Some(function), Some(function))`][then],
640    /// it has slightly different semantics than `then`:
641    /// - `on_finally` doesn't receive any argument, unlike `on_fulfilled` and `on_rejected`.
642    /// - `finally()` is transparent; a call like `Promise.resolve("first").finally(() => "second")`
643    ///   returns a promise fulfilled with the value `"first"`, which would return `"second"` if `finally`
644    ///   was a shortcut of `then`.
645    ///
646    /// # Examples
647    ///
648    /// ```
649    /// # use std::error::Error;
650    /// # use boa_engine::{
651    /// #     object::{builtins::JsPromise, FunctionObjectBuilder},
652    /// #     property::Attribute,
653    /// #     Context, JsNativeError, JsValue, NativeFunction, js_string
654    /// # };
655    /// # fn main() -> Result<(), Box<dyn Error>> {
656    /// let context = &mut Context::default();
657    ///
658    /// context.register_global_property(
659    ///     js_string!("finally"),
660    ///     false,
661    ///     Attribute::all(),
662    /// )?;
663    ///
664    /// let promise = JsPromise::new(
665    ///     |resolvers, context| {
666    ///         let error = JsNativeError::typ().with_message("thrown");
667    ///         let error = error.into_opaque(context);
668    ///         resolvers.reject.call(
669    ///             &JsValue::undefined(),
670    ///             &[error.into()],
671    ///             context,
672    ///         )?;
673    ///         Ok(JsValue::undefined())
674    ///     },
675    ///     context,
676    /// )?
677    /// .finally(
678    ///     NativeFunction::from_fn_ptr(|_, _, context| {
679    ///         context.global_object().clone().set(
680    ///             js_string!("finally"),
681    ///             JsValue::from(true),
682    ///             true,
683    ///             context,
684    ///         )?;
685    ///         Ok(JsValue::undefined())
686    ///     })
687    ///     .to_js_function(context.realm()),
688    ///     context,
689    /// )?;
690    ///
691    /// context.run_jobs();
692    ///
693    /// assert_eq!(
694    ///     context
695    ///         .global_object()
696    ///         .clone()
697    ///         .get(js_string!("finally"), context)?,
698    ///     JsValue::from(true)
699    /// );
700    ///
701    /// # Ok(())
702    /// # }
703    /// ```
704    ///
705    /// [`Promise.prototype.finally()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/finally
706    /// [then]: JsPromise::then
707    #[inline]
708    #[allow(clippy::return_self_not_must_use)] // Could just be used to add a handler on an existing promise
709    pub fn finally(&self, on_finally: JsFunction, context: &mut Context) -> JsResult<Self> {
710        let (then, catch) = Promise::then_catch_finally_closures(
711            context.intrinsics().constructors().promise().constructor(),
712            on_finally,
713            context,
714        );
715        Promise::inner_then(self, Some(then), Some(catch), context)
716            .and_then(Self::from_object)
717            .js_expect("`inner_then` cannot fail for native `JsPromise`")
718            .map_err(Into::into)
719    }
720
721    /// Waits for a list of promises to settle with fulfilled values, rejecting the aggregate promise
722    /// when any of the inner promises is rejected.
723    ///
724    /// Equivalent to the [`Promise.all`] static method.
725    ///
726    /// # Examples
727    ///
728    /// ```
729    /// # use std::error::Error;
730    /// # use boa_engine::{
731    /// #     js_string,
732    /// #     object::builtins::{JsArray, JsPromise},
733    /// #     Context, JsNativeError, JsValue,
734    /// # };
735    /// # fn main() -> Result<(), Box<dyn Error>> {
736    /// let context = &mut Context::default();
737    ///
738    /// let promise1 = JsPromise::all(
739    ///     [
740    ///         JsPromise::resolve(0, context)?,
741    ///         JsPromise::resolve(2, context)?,
742    ///         JsPromise::resolve(4, context)?,
743    ///     ],
744    ///     context,
745    /// )?;
746    ///
747    /// let promise2 = JsPromise::all(
748    ///     [
749    ///         JsPromise::resolve(1, context)?,
750    ///         JsPromise::reject(JsNativeError::typ(), context)?,
751    ///         JsPromise::resolve(3, context)?,
752    ///     ],
753    ///     context,
754    /// )?;
755    ///
756    /// context.run_jobs();
757    ///
758    /// let array = promise1
759    ///     .state()
760    ///     .as_fulfilled()
761    ///     .and_then(JsValue::as_object)
762    ///     .unwrap()
763    ///     .clone();
764    /// let array = JsArray::from_object(array)?;
765    /// assert_eq!(array.at(0, context)?, 0.into());
766    /// assert_eq!(array.at(1, context)?, 2.into());
767    /// assert_eq!(array.at(2, context)?, 4.into());
768    ///
769    /// let error = promise2.state().as_rejected().unwrap().clone();
770    /// assert_eq!(error.to_string(context)?, js_string!("TypeError"));
771    ///
772    /// # Ok(())
773    /// # }
774    /// ```
775    ///
776    /// [`Promise.all`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
777    pub fn all<I>(promises: I, context: &mut Context) -> JsResult<Self>
778    where
779        I: IntoIterator<Item = Self>,
780    {
781        let promises = JsArray::from_iter(promises.into_iter().map(JsValue::from), context);
782
783        let c = &context
784            .intrinsics()
785            .constructors()
786            .promise()
787            .constructor()
788            .into();
789
790        let value = Promise::all(c, &[promises.into()], context)
791            .js_expect("Promise.all cannot fail with the default `%Promise%` constructor")?;
792
793        let object = value
794            .as_object()
795            .js_expect("`Promise.all` always returns an object on success")?;
796
797        Self::from_object(object.clone())
798            .js_expect("`Promise::all` with the default `%Promise%` constructor always returns a native `JsPromise`").map_err(Into::into)
799    }
800
801    /// Waits for a list of promises to settle, fulfilling with an array of the outcomes of every
802    /// promise.
803    ///
804    /// Equivalent to the [`Promise.allSettled`] static method.
805    ///
806    /// # Examples
807    ///
808    /// ```
809    /// # use std::error::Error;
810    /// # use boa_engine::{
811    /// #     js_string,
812    /// #     object::builtins::{JsArray, JsPromise},
813    /// #     Context, JsNativeError, JsValue,
814    /// # };
815    /// # fn main() -> Result<(), Box<dyn Error>> {
816    /// let context = &mut Context::default();
817    ///
818    /// let promise = JsPromise::all_settled(
819    ///     [
820    ///         JsPromise::resolve(1, context)?,
821    ///         JsPromise::reject(JsNativeError::typ(), context)?,
822    ///         JsPromise::resolve(3, context)?,
823    ///     ],
824    ///     context,
825    /// )?;
826    ///
827    /// context.run_jobs();
828    ///
829    /// let array = promise
830    ///     .state()
831    ///     .as_fulfilled()
832    ///     .and_then(JsValue::as_object)
833    ///     .unwrap()
834    ///     .clone();
835    /// let array = JsArray::from_object(array)?;
836    ///
837    /// let a = array.at(0, context)?.as_object().unwrap().clone();
838    /// assert_eq!(
839    ///     a.get(js_string!("status"), context)?,
840    ///     js_string!("fulfilled").into()
841    /// );
842    /// assert_eq!(a.get(js_string!("value"), context)?, 1.into());
843    ///
844    /// let b = array.at(1, context)?.as_object().unwrap().clone();
845    /// assert_eq!(
846    ///     b.get(js_string!("status"), context)?,
847    ///     js_string!("rejected").into()
848    /// );
849    /// assert_eq!(
850    ///     b.get(js_string!("reason"), context)?.to_string(context)?,
851    ///     js_string!("TypeError")
852    /// );
853    ///
854    /// let c = array.at(2, context)?.as_object().unwrap().clone();
855    /// assert_eq!(
856    ///     c.get(js_string!("status"), context)?,
857    ///     js_string!("fulfilled").into()
858    /// );
859    /// assert_eq!(c.get(js_string!("value"), context)?, 3.into());
860    ///
861    /// # Ok(())
862    /// # }
863    /// ```
864    ///
865    /// [`Promise.allSettled`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled
866    pub fn all_settled<I>(promises: I, context: &mut Context) -> JsResult<Self>
867    where
868        I: IntoIterator<Item = Self>,
869    {
870        let promises = JsArray::from_iter(promises.into_iter().map(JsValue::from), context);
871
872        let c = &context
873            .intrinsics()
874            .constructors()
875            .promise()
876            .constructor()
877            .into();
878
879        let value = Promise::all_settled(c, &[promises.into()], context).js_expect(
880            "`Promise.all_settled` cannot fail with the default `%Promise%` constructor",
881        )?;
882
883        let object = value
884            .as_object()
885            .js_expect("`Promise.all_settled` always returns an object on success")?;
886
887        Self::from_object(object.clone())
888            .js_expect("`Promise::all_settled` with the default `%Promise%` constructor always returns a native `JsPromise`").map_err(Into::into)
889    }
890
891    /// Returns the first promise that fulfills from a list of promises.
892    ///
893    /// Equivalent to the [`Promise.any`] static method.
894    ///
895    /// If after settling all promises in `promises` there isn't a fulfilled promise, the returned
896    /// promise will be rejected with an `AggregatorError` containing the rejection values of every
897    /// promise; this includes the case where `promises` is an empty iterator.
898    ///
899    /// # Examples
900    ///
901    /// ```
902    /// # use std::error::Error;
903    /// # use boa_engine::{
904    /// #     builtins::promise::PromiseState,
905    /// #     js_string,
906    /// #     object::builtins::JsPromise,
907    /// #     Context, JsNativeError,
908    /// # };
909    /// # fn main() -> Result<(), Box<dyn Error>> {
910    /// let context = &mut Context::default();
911    ///
912    /// let promise = JsPromise::any(
913    ///     [
914    ///         JsPromise::reject(JsNativeError::syntax(), context)?,
915    ///         JsPromise::reject(JsNativeError::typ(), context)?,
916    ///         JsPromise::resolve(js_string!("fulfilled"), context)?,
917    ///         JsPromise::reject(JsNativeError::range(), context)?,
918    ///     ],
919    ///     context,
920    /// )?;
921    ///
922    /// context.run_jobs();
923    ///
924    /// assert_eq!(
925    ///     promise.state(),
926    ///     PromiseState::Fulfilled(js_string!("fulfilled").into())
927    /// );
928    /// # Ok(())
929    /// # }
930    /// ```
931    ///
932    /// [`Promise.any`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/any
933    pub fn any<I>(promises: I, context: &mut Context) -> JsResult<Self>
934    where
935        I: IntoIterator<Item = Self>,
936    {
937        let promises = JsArray::from_iter(promises.into_iter().map(JsValue::from), context);
938
939        let c = &context
940            .intrinsics()
941            .constructors()
942            .promise()
943            .constructor()
944            .into();
945
946        let value = Promise::any(c, &[promises.into()], context)
947            .js_expect("`Promise.any` cannot fail with the default `%Promise%` constructor")?;
948
949        let object = value
950            .as_object()
951            .js_expect("`Promise.any` always returns an object on success")?;
952
953        Self::from_object(object.clone())
954            .js_expect("`Promise::any` with the default `%Promise%` constructor always returns a native `JsPromise`").map_err(Into::into)
955    }
956
957    /// Returns the first promise that settles from a list of promises.
958    ///
959    /// Equivalent to the [`Promise.race`] static method.
960    ///
961    /// If the provided iterator is empty, the returned promise will remain on the pending state
962    /// forever.
963    ///
964    /// # Examples
965    ///
966    /// ```
967    /// # use std::error::Error;
968    /// # use boa_engine::{
969    /// #     builtins::promise::PromiseState,
970    /// #     js_string,
971    /// #     object::builtins::JsPromise,
972    /// #     Context, JsValue,
973    /// # };
974    /// # fn main() -> Result<(), Box<dyn Error>> {
975    /// let context = &mut Context::default();
976    ///
977    /// let (a, resolvers_a) = JsPromise::new_pending(context);
978    /// let (b, resolvers_b) = JsPromise::new_pending(context);
979    /// let (c, resolvers_c) = JsPromise::new_pending(context);
980    ///
981    /// let promise = JsPromise::race([a, b, c], context)?;
982    ///
983    /// resolvers_b
984    ///     .reject
985    ///     .call(&JsValue::undefined(), &[], context)?;
986    /// resolvers_a
987    ///     .resolve
988    ///     .call(&JsValue::undefined(), &[5.into()], context)?;
989    /// resolvers_c.reject.call(
990    ///     &JsValue::undefined(),
991    ///     &[js_string!("c error").into()],
992    ///     context,
993    /// )?;
994    ///
995    /// context.run_jobs();
996    ///
997    /// assert_eq!(
998    ///     promise.state(),
999    ///     PromiseState::Rejected(JsValue::undefined())
1000    /// );
1001    ///
1002    /// # Ok(())
1003    /// # }
1004    /// ```
1005    ///
1006    /// [`Promise.race`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race
1007    pub fn race<I>(promises: I, context: &mut Context) -> JsResult<Self>
1008    where
1009        I: IntoIterator<Item = Self>,
1010    {
1011        let promises = JsArray::from_iter(promises.into_iter().map(JsValue::from), context);
1012
1013        let c = &context
1014            .intrinsics()
1015            .constructors()
1016            .promise()
1017            .constructor()
1018            .into();
1019
1020        let value = Promise::race(c, &[promises.into()], context)
1021            .js_expect("`Promise.race` cannot fail with the default `%Promise%` constructor")?;
1022
1023        let object = value
1024            .as_object()
1025            .js_expect("`Promise.race` always returns an object on success")?;
1026
1027        Self::from_object(object.clone())
1028            .js_expect("`Promise::race` with the default `%Promise%` constructor always returns a native `JsPromise`").map_err(Into::into)
1029    }
1030
1031    /// Creates a `JsFuture` from this `JsPromise`.
1032    ///
1033    /// The returned `JsFuture` implements [`Future`], which means it can be `await`ed within Rust's
1034    /// async contexts (async functions and async blocks).
1035    ///
1036    /// # Examples
1037    ///
1038    /// ```
1039    /// # use std::error::Error;
1040    /// # use boa_engine::{
1041    /// #     builtins::promise::PromiseState,
1042    /// #     object::builtins::JsPromise,
1043    /// #     Context, JsValue, JsError
1044    /// # };
1045    /// # use futures_lite::future;
1046    /// # fn main() -> Result<(), Box<dyn Error>> {
1047    /// let context = &mut Context::default();
1048    ///
1049    /// let (promise, resolvers) = JsPromise::new_pending(context);
1050    /// let promise_future = promise.into_js_future(context)?;
1051    ///
1052    /// let future1 = async move { promise_future.await };
1053    ///
1054    /// let future2 = async move {
1055    ///     resolvers
1056    ///         .resolve
1057    ///         .call(&JsValue::undefined(), &[10.into()], context)?;
1058    ///     context.run_jobs();
1059    ///     Ok::<(), JsError>(())
1060    /// };
1061    ///
1062    /// let (result1, result2) = future::block_on(future::zip(future1, future2));
1063    ///
1064    /// assert_eq!(result1, Ok(JsValue::from(10)));
1065    /// assert_eq!(result2, Ok(()));
1066    ///
1067    /// # Ok(())
1068    /// # }
1069    /// ```
1070    pub fn into_js_future(self, context: &mut Context) -> JsResult<JsFuture> {
1071        // Mostly based from:
1072        // https://docs.rs/wasm-bindgen-futures/0.4.37/src/wasm_bindgen_futures/lib.rs.html#109-168
1073
1074        fn finish(state: &GcRefCell<Inner>, val: JsResult<JsValue>) {
1075            let task = {
1076                let mut state = state.borrow_mut();
1077
1078                // The engine ensures both `resolve` and `reject` are called only once,
1079                // and only one of them.
1080                debug_assert!(state.result.is_none());
1081
1082                // Store the received value into the state shared by the resolving functions
1083                // and the `JsFuture` itself. This will be accessed when the executor polls
1084                // the `JsFuture` again.
1085                state.result = Some(val);
1086                state.task.take()
1087            };
1088
1089            // `task` could be `None` if the `JsPromise` was already fulfilled before polling
1090            // the `JsFuture`.
1091            if let Some(task) = task {
1092                task.wake();
1093            }
1094        }
1095
1096        let state = Gc::new(GcRefCell::new(Inner {
1097            result: None,
1098            task: None,
1099        }));
1100
1101        let resolve = {
1102            let state = state.clone();
1103
1104            NativeFunction::from_copy_closure_with_captures(
1105                move |_, args, state, _| {
1106                    finish(state, Ok(args.get_or_undefined(0).clone()));
1107                    Ok(JsValue::undefined())
1108                },
1109                state,
1110            )
1111        };
1112
1113        let reject = {
1114            let state = state.clone();
1115
1116            NativeFunction::from_copy_closure_with_captures(
1117                move |_, args, state, _| {
1118                    let err = JsError::from_opaque(args.get_or_undefined(0).clone());
1119                    finish(state, Err(err));
1120                    Ok(JsValue::undefined())
1121                },
1122                state,
1123            )
1124        };
1125
1126        drop(self.then(
1127            Some(resolve.to_js_function(context.realm())),
1128            Some(reject.to_js_function(context.realm())),
1129            context,
1130        )?);
1131
1132        Ok(JsFuture { inner: state })
1133    }
1134
1135    /// Run jobs until this promise is resolved or rejected. This could
1136    /// result in an infinite loop if the promise is never resolved or
1137    /// rejected (e.g. with a [`boa_engine::job::JobExecutor`] that does
1138    /// not prioritize properly). If you need more control over how
1139    /// the promise handles timing out, consider using
1140    /// [`Context::run_jobs`] directly.
1141    ///
1142    /// Returns [`Result::Ok`] if the promise resolved, or [`Result::Err`]
1143    /// if the promise was rejected. If the promise was already resolved,
1144    /// [`Context::run_jobs`] is guaranteed to not be executed.
1145    ///
1146    /// # Examples
1147    ///
1148    /// ```
1149    /// # use boa_engine::{Context, JsArgs, JsValue, NativeFunction};
1150    /// # use boa_engine::object::builtins::{JsFunction, JsPromise};
1151    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1152    /// let context = &mut Context::default();
1153    ///
1154    /// let p1 = JsPromise::new(
1155    ///     |fns, context| {
1156    ///         fns.resolve
1157    ///             .call(&JsValue::undefined(), &[JsValue::new(1)], context)
1158    ///     },
1159    ///     context,
1160    /// )?;
1161    /// let p2 = p1.then(
1162    ///     Some(
1163    ///         NativeFunction::from_fn_ptr(|_, args, context| {
1164    ///             assert_eq!(*args.get_or_undefined(0), JsValue::new(1));
1165    ///             Ok(JsValue::new(2))
1166    ///         })
1167    ///         .to_js_function(context.realm()),
1168    ///     ),
1169    ///     None,
1170    ///     context,
1171    /// )?;
1172    ///
1173    /// assert_eq!(p2.await_blocking(context), Ok(JsValue::new(2)));
1174    /// # Ok(())
1175    /// # }
1176    /// ```
1177    ///
1178    /// This will not panic as `run_jobs()` is not executed.
1179    /// ```
1180    /// # use boa_engine::{Context, JsValue, NativeFunction};
1181    /// # use boa_engine::object::builtins::JsPromise;
1182    ///
1183    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1184    /// let context = &mut Context::default();
1185    /// let p1 = JsPromise::new(
1186    ///     |fns, context| fns.resolve.call(&JsValue::undefined(), &[], context),
1187    ///     context,
1188    /// )?
1189    /// .then(
1190    ///     Some(
1191    ///         NativeFunction::from_fn_ptr(|_, _, _| {
1192    ///             panic!("This will not happen.");
1193    ///         })
1194    ///         .to_js_function(context.realm()),
1195    ///     ),
1196    ///     None,
1197    ///     context,
1198    /// )?;
1199    /// let p2 = JsPromise::resolve(1, context)?;
1200    ///
1201    /// assert_eq!(p2.await_blocking(context), Ok(JsValue::new(1)));
1202    /// // Uncommenting the following line would panic.
1203    /// // context.run_jobs();
1204    /// # Ok(())
1205    /// # }
1206    /// ```
1207    pub fn await_blocking(&self, context: &mut Context) -> Result<JsValue, JsError> {
1208        loop {
1209            match self.state() {
1210                PromiseState::Pending => {
1211                    context.run_jobs()?;
1212                }
1213                PromiseState::Fulfilled(f) => break Ok(f),
1214                PromiseState::Rejected(r) => break Err(JsError::from_opaque(r)),
1215            }
1216        }
1217    }
1218
1219    #[cfg(feature = "experimental")]
1220    pub(crate) fn await_native(
1221        &self,
1222        continuation: crate::native_function::NativeCoroutine,
1223        context: &mut Context,
1224    ) {
1225        use crate::{
1226            builtins::{async_generator::AsyncGenerator, generator::GeneratorContext},
1227            js_string,
1228            object::FunctionObjectBuilder,
1229            vm::CompletionRecord,
1230        };
1231        use std::cell::Cell;
1232        use std::ops::ControlFlow;
1233
1234        // Clone the stack since we split it.
1235        let stack = context.vm.stack.clone();
1236        let gen_ctx = GeneratorContext::from_current(context, None);
1237        context.vm.stack = stack;
1238
1239        // 3. Let fulfilledClosure be a new Abstract Closure with parameters (value) that captures asyncContext and performs the following steps when called:
1240        // 4. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »).
1241        let on_fulfilled = FunctionObjectBuilder::new(
1242            context.realm(),
1243            NativeFunction::from_copy_closure_with_captures(
1244                |_this, args, captures, context| {
1245                    // a. Let prevContext be the running execution context.
1246                    // b. Suspend prevContext.
1247                    // c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context.
1248                    // d. Resume the suspended evaluation of asyncContext using NormalCompletion(value) as the result of the operation that suspended it.
1249
1250                    let continuation = &captures.0;
1251                    let mut r#gen = captures.1.take().js_expect("should only run once")?;
1252
1253                    // NOTE: We need to get the object before resuming, since it could clear the stack.
1254                    let async_generator = r#gen.async_generator_object()?;
1255
1256                    std::mem::swap(&mut context.vm.stack, &mut r#gen.stack);
1257                    let frame = r#gen
1258                        .call_frame
1259                        .take()
1260                        .js_expect("should have a call frame")?;
1261                    let fp = frame.fp;
1262                    let rp = frame.rp;
1263                    context.vm.push_frame(frame);
1264                    context.vm.frame_mut().fp = fp;
1265                    context.vm.frame_mut().rp = rp;
1266
1267                    match continuation.call(
1268                        CompletionRecord::Normal(args.get_or_undefined(0).clone()),
1269                        context,
1270                    ) {
1271                        ControlFlow::Continue(value) => JsPromise::resolve(value, context)?
1272                            .await_native(continuation.clone(), context),
1273                        ControlFlow::Break(Err(err)) => return Err(err),
1274                        ControlFlow::Break(Ok(())) => {}
1275                    }
1276
1277                    std::mem::swap(&mut context.vm.stack, &mut r#gen.stack);
1278                    r#gen.call_frame = context.vm.pop_frame();
1279                    assert!(r#gen.call_frame.is_some());
1280
1281                    if let Some(async_generator) = async_generator {
1282                        async_generator
1283                            .downcast_mut::<AsyncGenerator>()
1284                            .js_expect("must be async generator")?
1285                            .context = Some(r#gen);
1286                    }
1287
1288                    // e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context.
1289                    // f. Return undefined.
1290                    Ok(JsValue::undefined())
1291                },
1292                (continuation.clone(), Cell::new(Some(gen_ctx))),
1293            ),
1294        )
1295        .name(js_string!())
1296        .length(1)
1297        .build();
1298
1299        let stack = context.vm.stack.clone();
1300        let gen_ctx = GeneratorContext::from_current(context, None);
1301        context.vm.stack = stack;
1302
1303        // 5. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures asyncContext and performs the following steps when called:
1304        // 6. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »).
1305        let on_rejected = FunctionObjectBuilder::new(
1306            context.realm(),
1307            NativeFunction::from_copy_closure_with_captures(
1308                |_this, args, captures, context| {
1309                    // a. Let prevContext be the running execution context.
1310                    // b. Suspend prevContext.
1311                    // c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context.
1312                    // d. Resume the suspended evaluation of asyncContext using ThrowCompletion(reason) as the result of the operation that suspended it.
1313                    // e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context.
1314                    // f. Return undefined.
1315
1316                    let continuation = &captures.0;
1317                    let mut r#gen = captures.1.take().js_expect("should only run once")?;
1318
1319                    // NOTE: We need to get the object before resuming, since it could clear the stack.
1320                    let async_generator = r#gen.async_generator_object()?;
1321
1322                    std::mem::swap(&mut context.vm.stack, &mut r#gen.stack);
1323                    let frame = r#gen
1324                        .call_frame
1325                        .take()
1326                        .js_expect("should have a call frame")?;
1327                    let fp = frame.fp;
1328                    let rp = frame.rp;
1329                    context.vm.push_frame(frame);
1330                    context.vm.frame_mut().fp = fp;
1331                    context.vm.frame_mut().rp = rp;
1332
1333                    match continuation.call(
1334                        CompletionRecord::Throw(JsError::from_opaque(
1335                            args.get_or_undefined(0).clone(),
1336                        )),
1337                        context,
1338                    ) {
1339                        ControlFlow::Continue(value) => JsPromise::resolve(value, context)?
1340                            .await_native(continuation.clone(), context),
1341                        ControlFlow::Break(Err(err)) => return Err(err),
1342                        ControlFlow::Break(Ok(())) => {}
1343                    }
1344
1345                    std::mem::swap(&mut context.vm.stack, &mut r#gen.stack);
1346                    r#gen.call_frame = context.vm.pop_frame();
1347                    assert!(r#gen.call_frame.is_some());
1348
1349                    if let Some(async_generator) = async_generator {
1350                        async_generator
1351                            .downcast_mut::<AsyncGenerator>()
1352                            .js_expect("must be async generator")?
1353                            .context = Some(r#gen);
1354                    }
1355
1356                    Ok(JsValue::undefined())
1357                },
1358                (continuation, Cell::new(Some(gen_ctx))),
1359            ),
1360        )
1361        .name(js_string!())
1362        .length(1)
1363        .build();
1364
1365        // 7. Perform PerformPromiseThen(promise, onFulfilled, onRejected).
1366        Promise::perform_promise_then(
1367            &self.inner,
1368            Some(on_fulfilled),
1369            Some(on_rejected),
1370            None,
1371            context,
1372        );
1373    }
1374}
1375
1376impl From<JsObject<Promise>> for JsPromise {
1377    fn from(value: JsObject<Promise>) -> Self {
1378        Self { inner: value }
1379    }
1380}
1381
1382impl From<JsPromise> for JsObject {
1383    #[inline]
1384    fn from(o: JsPromise) -> Self {
1385        o.inner.clone().upcast()
1386    }
1387}
1388
1389impl From<JsPromise> for JsValue {
1390    #[inline]
1391    fn from(o: JsPromise) -> Self {
1392        o.inner.clone().into()
1393    }
1394}
1395
1396impl std::ops::Deref for JsPromise {
1397    type Target = JsObject<Promise>;
1398
1399    #[inline]
1400    fn deref(&self) -> &Self::Target {
1401        &self.inner
1402    }
1403}
1404
1405impl TryFromJs for JsPromise {
1406    fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult<Self> {
1407        if let Some(o) = value.as_object() {
1408            Self::from_object(o.clone())
1409        } else {
1410            Err(JsNativeError::typ()
1411                .with_message("value is not a Promise object")
1412                .into())
1413        }
1414    }
1415}
1416
1417impl TryIntoJs for JsPromise {
1418    fn try_into_js(&self, _: &mut Context) -> JsResult<JsValue> {
1419        Ok(self.clone().into())
1420    }
1421}
1422
1423/// A Rust's `Future` that becomes ready when a `JsPromise` fulfills.
1424///
1425/// This type allows `await`ing `JsPromise`s inside Rust's async contexts, which makes interfacing
1426/// between promises and futures a bit easier.
1427///
1428/// The only way to construct an instance of `JsFuture` is by calling [`JsPromise::into_js_future`].
1429pub struct JsFuture {
1430    inner: Gc<GcRefCell<Inner>>,
1431}
1432
1433impl std::fmt::Debug for JsFuture {
1434    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1435        f.debug_struct("JsFuture").finish_non_exhaustive()
1436    }
1437}
1438
1439#[derive(Trace, Finalize)]
1440struct Inner {
1441    result: Option<JsResult<JsValue>>,
1442    #[unsafe_ignore_trace]
1443    task: Option<task::Waker>,
1444}
1445
1446// Taken from:
1447// https://docs.rs/wasm-bindgen-futures/0.4.37/src/wasm_bindgen_futures/lib.rs.html#171-187
1448impl Future for JsFuture {
1449    type Output = JsResult<JsValue>;
1450
1451    fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll<Self::Output> {
1452        let mut inner = self.inner.borrow_mut();
1453
1454        if let Some(result) = inner.result.take() {
1455            return task::Poll::Ready(result);
1456        }
1457
1458        inner.task = Some(cx.waker().clone());
1459        task::Poll::Pending
1460    }
1461}