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}