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.state.is_none() {
194 let inner = Rc::new(RefCell::new(cx.waker().clone()));
195 this.state = Some(inner.clone());
196
197 let resolve = Function::new(this.promise.ctx.clone(), move || {
198 inner.borrow().wake_by_ref();
199 })?;
200
201 this.promise.then()?.call::<_, ()>((
202 This(this.promise.clone()),
203 resolve.clone(),
204 resolve,
205 ))?;
206 return Poll::Pending;
207 }
208
209 this.state
210 .as_ref()
211 .unwrap()
212 .borrow_mut()
213 .clone_from(cx.waker());
214
215 Poll::Pending
216 }
217}
218
219#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "futures")))]
221#[repr(transparent)]
222#[cfg(feature = "futures")]
223pub struct Promised<T>(pub T);
224
225#[cfg(feature = "futures")]
226impl<T> From<T> for Promised<T> {
227 fn from(future: T) -> Self {
228 Self(future)
229 }
230}
231
232#[cfg(feature = "futures")]
233impl<'js, T, R> IntoJs<'js> for Promised<T>
234where
235 T: Future<Output = R> + 'js,
236 R: IntoJs<'js> + 'js,
237{
238 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
239 Promise::wrap_future(ctx, self.0).map(|x| x.into_value())
240 }
241}
242
243#[derive(Debug, PartialEq, Clone, Hash, Eq)]
250pub struct MaybePromise<'js>(Value<'js>);
251
252impl<'js> FromJs<'js> for MaybePromise<'js> {
253 fn from_js(_ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
254 Ok(MaybePromise(value))
255 }
256}
257
258impl<'js> IntoJs<'js> for MaybePromise<'js> {
259 fn into_js(self, _ctx: &Ctx<'js>) -> Result<Value<'js>> {
260 Ok(self.0)
261 }
262}
263
264impl<'js> MaybePromise<'js> {
265 pub fn as_value(&self) -> &Value<'js> {
267 &self.0
268 }
269
270 pub fn into_value(self) -> Value<'js> {
272 self.0
273 }
274
275 pub fn from_value(value: Value<'js>) -> Self {
277 MaybePromise(value)
278 }
279
280 pub fn ctx(&self) -> &Ctx<'js> {
282 self.0.ctx()
283 }
284
285 pub fn state(&self) -> PromiseState {
288 if let Some(x) = self.0.as_promise() {
289 x.state()
290 } else {
291 PromiseState::Resolved
292 }
293 }
294
295 pub fn result<T: FromJs<'js>>(&self) -> Option<Result<T>> {
297 if let Some(x) = self.0.as_promise() {
298 x.result::<T>()
299 } else {
300 Some(T::from_js(self.0.ctx(), self.0.clone()))
301 }
302 }
303
304 pub fn finish<T: FromJs<'js>>(&self) -> Result<T> {
306 if let Some(x) = self.0.as_promise() {
307 x.finish::<T>()
308 } else {
309 T::from_js(self.0.ctx(), self.0.clone())
310 }
311 }
312
313 #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "futures")))]
317 #[cfg(feature = "futures")]
318 pub fn into_future<T: FromJs<'js>>(self) -> MaybePromiseFuture<'js, T> {
319 if self.0.is_promise() {
320 let fut = self.0.into_promise().unwrap().into_future();
321 MaybePromiseFuture(MaybePromiseFutureInner::Future(fut))
322 } else {
323 MaybePromiseFuture(MaybePromiseFutureInner::Ready(self.0))
324 }
325 }
326}
327
328#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "futures")))]
330#[cfg(feature = "futures")]
331#[must_use = "futures do nothing unless you `.await` or poll them"]
332#[derive(Debug)]
333pub struct MaybePromiseFuture<'js, T>(MaybePromiseFutureInner<'js, T>);
334
335#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "futures")))]
336#[cfg(feature = "futures")]
337#[derive(Debug)]
338enum MaybePromiseFutureInner<'js, T> {
339 Ready(Value<'js>),
340 Future(PromiseFuture<'js, T>),
341}
342
343#[cfg(feature = "futures")]
344impl<'js, T> Future for MaybePromiseFuture<'js, T>
345where
346 T: FromJs<'js>,
347{
348 type Output = Result<T>;
349
350 fn poll(self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll<Self::Output> {
351 match self.get_mut().0 {
352 MaybePromiseFutureInner::Ready(ref x) => Poll::Ready(T::from_js(x.ctx(), x.clone())),
353 MaybePromiseFutureInner::Future(ref mut x) => Pin::new(x).poll(cx),
354 }
355 }
356}
357
358#[cfg(test)]
359mod test {
360 use std::sync::atomic::{AtomicBool, Ordering};
361 #[cfg(feature = "futures")]
362 use std::time::Duration;
363
364 use super::Promise;
365 #[cfg(feature = "futures")]
366 use crate::{
367 async_with, function::Async, promise::Promised, AsyncContext, AsyncRuntime, CaughtError,
368 Result,
369 };
370 use crate::{
371 function::Func, prelude::This, promise::PromiseState, CatchResultExt, Context, Function,
372 Runtime,
373 };
374
375 #[cfg(feature = "futures")]
376 async fn set_timeout<'js>(cb: Function<'js>, number: f64) -> Result<()> {
377 tokio::time::sleep(Duration::from_secs_f64(number / 1000.0)).await;
378 cb.call::<_, ()>(())
379 }
380
381 #[cfg(feature = "futures")]
382 #[tokio::test]
383 async fn promise() {
384 let rt = AsyncRuntime::new().unwrap();
385 let ctx = AsyncContext::full(&rt).await.unwrap();
386
387 async_with!(ctx => |ctx| {
388 ctx.globals().set("setTimeout",Func::from(Async(set_timeout))).unwrap();
389
390 let func = ctx
391 .eval::<Function, _>(
392 r"
393 (function(){
394 return new Promise((resolve) => {
395 setTimeout(x => {
396 resolve(42)
397 },100)
398 })
399 })
400 ",
401 )
402 .catch(&ctx)
403 .unwrap();
404 let promise: Promise = func.call(()).unwrap();
405 assert_eq!(promise.into_future::<i32>().await.catch(&ctx).unwrap(), 42);
406
407 let func = ctx
408 .eval::<Function, _>(
409 r"
410 (function(){
411 return new Promise((_,reject) => {
412 setTimeout(x => {
413 reject(42)
414 },100)
415 })
416 })
417 ",
418 )
419 .catch(&ctx)
420 .unwrap();
421 let promise: Promise = func.call(()).unwrap();
422 let err = promise.into_future::<()>().await.catch(&ctx);
423 match err {
424 Err(CaughtError::Value(v)) => {
425 assert_eq!(v.as_int().unwrap(), 42)
426 }
427 _ => panic!(),
428 }
429 })
430 .await
431 }
432
433 #[cfg(feature = "futures")]
434 #[tokio::test]
435 async fn promised() {
436 use crate::Exception;
437
438 let rt = AsyncRuntime::new().unwrap();
439 let ctx = AsyncContext::full(&rt).await.unwrap();
440
441 async_with!(ctx => |ctx| {
442 let promised = Promised::from(async {
443 tokio::time::sleep(Duration::from_millis(100)).await;
444 42
445 });
446
447 let function = ctx.eval::<Function,_>(r"
448 (async function(v){
449 let val = await v;
450 if(val !== 42){
451 throw new Error('not correct value')
452 }
453 })
454 ").catch(&ctx).unwrap();
455
456 function.call::<_,Promise>((promised,)).unwrap().into_future::<()>().await.unwrap();
457
458 let ctx_clone = ctx.clone();
459 let promised = Promised::from(async move {
460 tokio::time::sleep(Duration::from_millis(100)).await;
461 Result::<()>::Err(Exception::throw_message(&ctx_clone, "some_message"))
462 });
463
464 let function = ctx.eval::<Function,_>(r"
465 (async function(v){
466 try{
467 await v;
468 }catch(e) {
469 if (e.message !== 'some_message'){
470 throw new Error('wrong error')
471 }
472 return
473 }
474 throw new Error('no error thrown')
475 })
476 ")
477 .catch(&ctx)
478 .unwrap();
479
480
481 function.call::<_,Promise>((promised,)).unwrap().into_future::<()>().await.unwrap()
482 })
483 .await
484 }
485
486 #[test]
487 fn promise_then() {
488 static DID_EXECUTE: AtomicBool = AtomicBool::new(false);
489
490 let rt = Runtime::new().unwrap();
491 let ctx = Context::full(&rt).unwrap();
492
493 ctx.with(|ctx| {
494 let (promise, resolve, _) = Promise::new(&ctx).unwrap();
495
496 let cb = Func::new(|s: String| {
497 assert_eq!(s, "FOO");
498 DID_EXECUTE.store(true, Ordering::SeqCst);
499 });
500
501 assert_eq!(promise.state(), PromiseState::Pending);
502
503 promise
504 .get::<_, Function>("then")
505 .catch(&ctx)
506 .unwrap()
507 .call::<_, ()>((This(promise.clone()), cb))
508 .catch(&ctx)
509 .unwrap();
510
511 resolve.call::<_, ()>(("FOO",)).unwrap();
512 assert_eq!(promise.state(), PromiseState::Resolved);
513
514 while ctx.execute_pending_job() {}
515
516 assert!(DID_EXECUTE.load(Ordering::SeqCst));
517 })
518 }
519}