1use crate::{
3 atom::PredefinedAtom, qjs, Ctx, Error, FromJs, Function, IntoJs, Object, Result, Value,
4};
5#[cfg(feature = "futures")]
6use crate::{function::This, CatchResultExt, CaughtError};
7#[cfg(feature = "futures")]
8use alloc::rc::Rc;
9#[cfg(feature = "futures")]
10use core::{
11 cell::RefCell,
12 future::Future,
13 marker::PhantomData,
14 pin::Pin,
15 task::{Context as TaskContext, Poll, Waker},
16};
17#[cfg(all(feature = "std", feature = "futures"))]
18use std::println;
19
20#[derive(Clone, Copy, Eq, PartialEq, Debug)]
22pub enum PromiseState {
23 Pending,
25 Resolved,
27 Rejected,
29}
30
31#[derive(Clone, Copy, Eq, PartialEq, Debug)]
33pub enum PromiseHookType {
34 Init,
35 Before,
36 After,
37 Resolve,
38}
39
40#[derive(Debug, PartialEq, Clone, Hash, Eq)]
42#[repr(transparent)]
43pub struct Promise<'js>(pub(crate) Object<'js>);
44
45impl<'js> Promise<'js> {
46 #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "futures")))]
47 #[cfg(feature = "futures")]
48 pub fn wrap_future<F, R>(ctx: &Ctx<'js>, future: F) -> Result<Self>
49 where
50 F: Future<Output = R> + 'js,
51 R: IntoJs<'js>,
52 {
53 let (promise, resolve, reject) = ctx.promise()?;
54 let ctx_clone = ctx.clone();
55 let future = async move {
56 let res = future.await.into_js(&ctx_clone).catch(&ctx_clone);
57
58 let err = match res {
59 Ok(x) => resolve.call::<_, ()>((x,)),
60 Err(e) => match e {
61 CaughtError::Exception(e) => reject.call::<_, ()>((e,)),
62 CaughtError::Value(e) => reject.call::<_, ()>((e,)),
63 CaughtError::Error(e) => {
64 let is_exception = unsafe { qjs::JS_IsException(e.throw(&ctx_clone)) };
65 debug_assert!(is_exception);
66 let e = ctx_clone.catch();
67 reject.call::<_, ()>((e,))
68 }
69 },
70 };
71 if let Err(_e) = err {
73 #[cfg(feature = "std")]
74 println!("promise handle function returned error:{}", _e);
75 }
76 };
77 ctx.spawn(future);
78 Ok(promise)
79 }
80
81 pub fn new(ctx: &Ctx<'js>) -> Result<(Self, Function<'js>, Function<'js>)> {
83 ctx.promise()
84 }
85
86 pub fn state(&self) -> PromiseState {
88 let v = unsafe { qjs::JS_PromiseState(self.ctx().as_ptr(), self.as_js_value()) };
89 match v {
90 qjs::JSPromiseStateEnum_JS_PROMISE_PENDING => PromiseState::Pending,
91 qjs::JSPromiseStateEnum_JS_PROMISE_FULFILLED => PromiseState::Resolved,
92 qjs::JSPromiseStateEnum_JS_PROMISE_REJECTED => PromiseState::Rejected,
93 _ => unreachable!(),
94 }
95 }
96
97 pub fn then(&self) -> Result<Function<'js>> {
99 self.0.get(PredefinedAtom::Then)
100 }
101
102 pub fn catch(&self) -> Result<Function<'js>> {
104 self.0.get(PredefinedAtom::Catch)
105 }
106
107 pub fn result<T: FromJs<'js>>(&self) -> Option<Result<T>> {
113 match self.state() {
114 PromiseState::Pending => None,
115 PromiseState::Resolved => {
116 let v = unsafe { qjs::JS_PromiseResult(self.ctx().as_ptr(), self.as_js_value()) };
117 let v = unsafe { Value::from_js_value(self.ctx().clone(), v) };
118 Some(FromJs::from_js(self.ctx(), v))
119 }
120 PromiseState::Rejected => {
121 unsafe {
122 let v = qjs::JS_PromiseResult(self.ctx().as_ptr(), self.as_js_value());
123 qjs::JS_Throw(self.ctx().as_ptr(), v);
124 };
125 Some(Err(Error::Exception))
126 }
127 }
128 }
129
130 pub fn finish<T: FromJs<'js>>(&self) -> Result<T> {
138 loop {
139 if let Some(x) = self.result() {
140 return x;
141 }
142
143 if !self.ctx.execute_pending_job() {
144 return Err(Error::WouldBlock);
145 }
146 }
147 }
148
149 #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "futures")))]
151 #[cfg(feature = "futures")]
152 pub fn into_future<T>(self) -> PromiseFuture<'js, T>
153 where
154 T: FromJs<'js>,
155 {
156 PromiseFuture {
157 state: None,
158 promise: self,
159 _marker: PhantomData,
160 }
161 }
162}
163
164#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "futures")))]
166#[cfg(feature = "futures")]
167#[must_use = "futures do nothing unless you `.await` or poll them"]
168#[derive(Debug)]
169pub struct PromiseFuture<'js, T> {
170 state: Option<Rc<RefCell<Waker>>>,
171 promise: Promise<'js>,
172 _marker: PhantomData<T>,
173}
174
175#[cfg(feature = "futures")]
177impl<'js, T> Unpin for PromiseFuture<'js, T> {}
178
179#[cfg(feature = "futures")]
180impl<'js, T> Future for PromiseFuture<'js, T>
181where
182 T: FromJs<'js>,
183{
184 type Output = Result<T>;
185
186 fn poll(self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll<Self::Output> {
187 let this = self.get_mut();
188
189 if let Some(x) = this.promise.result() {
190 return Poll::Ready(x);
191 }
192
193 if this.promise.ctx.has_exception() {
196 let exc = this.promise.ctx.catch();
197 if exc.is_uncatchable_error() {
198 this.promise.ctx.throw(exc);
199 return Poll::Ready(Err(Error::Exception));
200 }
201 }
202
203 if this.state.is_none() {
204 let inner = Rc::new(RefCell::new(cx.waker().clone()));
205 this.state = Some(inner.clone());
206
207 let resolve = Function::new(this.promise.ctx.clone(), move || {
208 inner.borrow().wake_by_ref();
209 })?;
210
211 this.promise.then()?.call::<_, ()>((
212 This(this.promise.clone()),
213 resolve.clone(),
214 resolve,
215 ))?;
216 return Poll::Pending;
217 }
218
219 this.state
220 .as_ref()
221 .unwrap()
222 .borrow_mut()
223 .clone_from(cx.waker());
224
225 Poll::Pending
226 }
227}
228
229#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "futures")))]
231#[repr(transparent)]
232#[cfg(feature = "futures")]
233pub struct Promised<T>(pub T);
234
235#[cfg(feature = "futures")]
236impl<T> From<T> for Promised<T> {
237 fn from(future: T) -> Self {
238 Self(future)
239 }
240}
241
242#[cfg(feature = "futures")]
243impl<'js, T, R> IntoJs<'js> for Promised<T>
244where
245 T: Future<Output = R> + 'js,
246 R: IntoJs<'js> + 'js,
247{
248 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
249 Promise::wrap_future(ctx, self.0).map(|x| x.into_value())
250 }
251}
252
253#[derive(Debug, PartialEq, Clone, Hash, Eq)]
260pub struct MaybePromise<'js>(Value<'js>);
261
262impl<'js> FromJs<'js> for MaybePromise<'js> {
263 fn from_js(_ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
264 Ok(MaybePromise(value))
265 }
266}
267
268impl<'js> IntoJs<'js> for MaybePromise<'js> {
269 fn into_js(self, _ctx: &Ctx<'js>) -> Result<Value<'js>> {
270 Ok(self.0)
271 }
272}
273
274impl<'js> MaybePromise<'js> {
275 pub fn as_value(&self) -> &Value<'js> {
277 &self.0
278 }
279
280 pub fn into_value(self) -> Value<'js> {
282 self.0
283 }
284
285 pub fn from_value(value: Value<'js>) -> Self {
287 MaybePromise(value)
288 }
289
290 pub fn ctx(&self) -> &Ctx<'js> {
292 self.0.ctx()
293 }
294
295 pub fn state(&self) -> PromiseState {
298 if let Some(x) = self.0.as_promise() {
299 x.state()
300 } else {
301 PromiseState::Resolved
302 }
303 }
304
305 pub fn result<T: FromJs<'js>>(&self) -> Option<Result<T>> {
307 if let Some(x) = self.0.as_promise() {
308 x.result::<T>()
309 } else {
310 Some(T::from_js(self.0.ctx(), self.0.clone()))
311 }
312 }
313
314 pub fn finish<T: FromJs<'js>>(&self) -> Result<T> {
316 if let Some(x) = self.0.as_promise() {
317 x.finish::<T>()
318 } else {
319 T::from_js(self.0.ctx(), self.0.clone())
320 }
321 }
322
323 #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "futures")))]
327 #[cfg(feature = "futures")]
328 pub fn into_future<T: FromJs<'js>>(self) -> MaybePromiseFuture<'js, T> {
329 if self.0.is_promise() {
330 let fut = self.0.into_promise().unwrap().into_future();
331 MaybePromiseFuture(MaybePromiseFutureInner::Future(fut))
332 } else {
333 MaybePromiseFuture(MaybePromiseFutureInner::Ready(self.0))
334 }
335 }
336}
337
338#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "futures")))]
340#[cfg(feature = "futures")]
341#[must_use = "futures do nothing unless you `.await` or poll them"]
342#[derive(Debug)]
343pub struct MaybePromiseFuture<'js, T>(MaybePromiseFutureInner<'js, T>);
344
345#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "futures")))]
346#[cfg(feature = "futures")]
347#[derive(Debug)]
348enum MaybePromiseFutureInner<'js, T> {
349 Ready(Value<'js>),
350 Future(PromiseFuture<'js, T>),
351}
352
353#[cfg(feature = "futures")]
354impl<'js, T> Future for MaybePromiseFuture<'js, T>
355where
356 T: FromJs<'js>,
357{
358 type Output = Result<T>;
359
360 fn poll(self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll<Self::Output> {
361 match self.get_mut().0 {
362 MaybePromiseFutureInner::Ready(ref x) => Poll::Ready(T::from_js(x.ctx(), x.clone())),
363 MaybePromiseFutureInner::Future(ref mut x) => Pin::new(x).poll(cx),
364 }
365 }
366}
367
368#[cfg(test)]
369mod test {
370 use std::sync::atomic::{AtomicBool, Ordering};
371 #[cfg(feature = "futures")]
372 use std::time::Duration;
373
374 use super::Promise;
375 #[cfg(feature = "futures")]
376 use crate::{
377 function::Async, promise::Promised, AsyncContext, AsyncRuntime, CaughtError, Result,
378 };
379 use crate::{
380 function::Func, prelude::This, promise::PromiseState, CatchResultExt, Context, Function,
381 Runtime,
382 };
383
384 #[cfg(feature = "futures")]
385 async fn set_timeout<'js>(cb: Function<'js>, number: f64) -> Result<()> {
386 tokio::time::sleep(Duration::from_secs_f64(number / 1000.0)).await;
387 cb.call::<_, ()>(())
388 }
389
390 #[cfg(feature = "futures")]
391 #[tokio::test]
392 async fn promise() {
393 let rt = AsyncRuntime::new().unwrap();
394 let ctx = AsyncContext::full(&rt).await.unwrap();
395
396 ctx.async_with(async |ctx| {
397 ctx.globals()
398 .set("setTimeout", Func::from(Async(set_timeout)))
399 .unwrap();
400
401 let func = ctx
402 .eval::<Function, _>(
403 r"
404 (function(){
405 return new Promise((resolve) => {
406 setTimeout(x => {
407 resolve(42)
408 },100)
409 })
410 })
411 ",
412 )
413 .catch(&ctx)
414 .unwrap();
415 let promise: Promise = func.call(()).unwrap();
416 assert_eq!(promise.into_future::<i32>().await.catch(&ctx).unwrap(), 42);
417
418 let func = ctx
419 .eval::<Function, _>(
420 r"
421 (function(){
422 return new Promise((_,reject) => {
423 setTimeout(x => {
424 reject(42)
425 },100)
426 })
427 })
428 ",
429 )
430 .catch(&ctx)
431 .unwrap();
432 let promise: Promise = func.call(()).unwrap();
433 let err = promise.into_future::<()>().await.catch(&ctx);
434 match err {
435 Err(CaughtError::Value(v)) => {
436 assert_eq!(v.as_int().unwrap(), 42)
437 }
438 _ => panic!(),
439 }
440 })
441 .await
442 }
443
444 #[cfg(feature = "futures")]
445 #[tokio::test]
446 async fn promised() {
447 use crate::Exception;
448
449 let rt = AsyncRuntime::new().unwrap();
450 let ctx = AsyncContext::full(&rt).await.unwrap();
451
452 ctx.async_with(async |ctx| {
453 let promised = Promised::from(async {
454 tokio::time::sleep(Duration::from_millis(100)).await;
455 42
456 });
457
458 let function = ctx
459 .eval::<Function, _>(
460 r"
461 (async function(v){
462 let val = await v;
463 if(val !== 42){
464 throw new Error('not correct value')
465 }
466 })
467 ",
468 )
469 .catch(&ctx)
470 .unwrap();
471
472 function
473 .call::<_, Promise>((promised,))
474 .unwrap()
475 .into_future::<()>()
476 .await
477 .unwrap();
478
479 let ctx_clone = ctx.clone();
480 let promised = Promised::from(async move {
481 tokio::time::sleep(Duration::from_millis(100)).await;
482 Result::<()>::Err(Exception::throw_message(&ctx_clone, "some_message"))
483 });
484
485 let function = ctx
486 .eval::<Function, _>(
487 r"
488 (async function(v){
489 try{
490 await v;
491 }catch(e) {
492 if (e.message !== 'some_message'){
493 throw new Error('wrong error')
494 }
495 return
496 }
497 throw new Error('no error thrown')
498 })
499 ",
500 )
501 .catch(&ctx)
502 .unwrap();
503
504 function
505 .call::<_, Promise>((promised,))
506 .unwrap()
507 .into_future::<()>()
508 .await
509 .unwrap()
510 })
511 .await
512 }
513
514 #[test]
515 fn promise_then() {
516 static DID_EXECUTE: AtomicBool = AtomicBool::new(false);
517
518 let rt = Runtime::new().unwrap();
519 let ctx = Context::full(&rt).unwrap();
520
521 ctx.with(|ctx| {
522 let (promise, resolve, _) = Promise::new(&ctx).unwrap();
523
524 let cb = Func::new(|s: String| {
525 assert_eq!(s, "FOO");
526 DID_EXECUTE.store(true, Ordering::SeqCst);
527 });
528
529 assert_eq!(promise.state(), PromiseState::Pending);
530
531 promise
532 .get::<_, Function>("then")
533 .catch(&ctx)
534 .unwrap()
535 .call::<_, ()>((This(promise.clone()), cb))
536 .catch(&ctx)
537 .unwrap();
538
539 resolve.call::<_, ()>(("FOO",)).unwrap();
540 assert_eq!(promise.state(), PromiseState::Resolved);
541
542 while ctx.execute_pending_job() {}
543
544 assert!(DID_EXECUTE.load(Ordering::SeqCst));
545 })
546 }
547
548 #[cfg(feature = "futures")]
549 #[tokio::test]
550 async fn promise_resolves_despite_stale_exception() {
551 let rt = AsyncRuntime::new().unwrap();
552 let ctx = AsyncContext::full(&rt).await.unwrap();
553
554 ctx.async_with(async |ctx| {
555 let _ = ctx.eval::<(), _>("throw new Error('stale')");
557
558 let func = ctx
559 .eval::<Function, _>(
560 r"
561 (function(){
562 return new Promise((resolve) => resolve(42))
563 })
564 ",
565 )
566 .catch(&ctx)
567 .unwrap();
568 let promise: Promise = func.call(()).unwrap();
569 assert_eq!(promise.into_future::<i32>().await.catch(&ctx).unwrap(), 42);
570 })
571 .await
572 }
573
574 #[cfg(feature = "futures")]
575 #[tokio::test]
576 async fn promise_fails_on_interrupt_exception() {
577 use std::sync::atomic::AtomicUsize;
578
579 let rt = AsyncRuntime::new().unwrap();
580 let counter = AtomicUsize::new(0);
581 rt.set_interrupt_handler(Some(Box::new(move || {
582 counter.fetch_add(1, Ordering::Relaxed) > 10
583 })))
584 .await;
585 let ctx = AsyncContext::full(&rt).await.unwrap();
586
587 ctx.async_with(async |ctx| {
588 let func = ctx
589 .eval::<Function, _>(
590 r"
591 (function(){
592 return new Promise((resolve) => {
593 while(true){}
594 resolve(42)
595 })
596 })
597 ",
598 )
599 .catch(&ctx)
600 .unwrap();
601 let promise: Promise = func.call(()).catch(&ctx).unwrap();
602 let result = promise.into_future::<i32>().await;
603 assert!(result.is_err(), "should fail due to interrupt");
604 })
605 .await
606 }
607}