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 std::{
9 cell::RefCell,
10 future::Future,
11 marker::PhantomData,
12 pin::Pin,
13 rc::Rc,
14 task::{Context as TaskContext, Poll, Waker},
15};
16
17#[derive(Clone, Copy, Eq, PartialEq, Debug)]
19pub enum PromiseState {
20 Pending,
22 Resolved,
24 Rejected,
26}
27
28#[derive(Debug, PartialEq, Clone, Hash, Eq)]
30#[repr(transparent)]
31pub struct Promise<'js>(pub(crate) Object<'js>);
32
33impl<'js> Promise<'js> {
34 #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "futures")))]
35 #[cfg(feature = "futures")]
36 pub fn wrap_future<F, R>(ctx: &Ctx<'js>, future: F) -> Result<Self>
37 where
38 F: Future<Output = R> + 'js,
39 R: IntoJs<'js>,
40 {
41 let (promise, resolve, reject) = ctx.promise()?;
42 let ctx_clone = ctx.clone();
43 let future = async move {
44 let res = future.await.into_js(&ctx_clone).catch(&ctx_clone);
45
46 let err = match res {
47 Ok(x) => resolve.call::<_, ()>((x,)),
48 Err(e) => match e {
49 CaughtError::Exception(e) => reject.call::<_, ()>((e,)),
50 CaughtError::Value(e) => reject.call::<_, ()>((e,)),
51 CaughtError::Error(e) => {
52 let is_exception = unsafe { qjs::JS_IsException(e.throw(&ctx_clone)) };
53 debug_assert!(is_exception);
54 let e = ctx_clone.catch();
55 reject.call::<_, ()>((e,))
56 }
57 },
58 };
59 if let Err(e) = err {
61 println!("promise handle function returned error:{}", e);
62 }
63 };
64 ctx.spawn(future);
65 Ok(promise)
66 }
67
68 pub fn new(ctx: &Ctx<'js>) -> Result<(Self, Function<'js>, Function<'js>)> {
70 ctx.promise()
71 }
72
73 pub fn state(&self) -> PromiseState {
75 let v = unsafe { qjs::JS_PromiseState(self.ctx().as_ptr(), self.as_js_value()) };
76 match v {
77 qjs::JSPromiseStateEnum_JS_PROMISE_PENDING => PromiseState::Pending,
78 qjs::JSPromiseStateEnum_JS_PROMISE_FULFILLED => PromiseState::Resolved,
79 qjs::JSPromiseStateEnum_JS_PROMISE_REJECTED => PromiseState::Rejected,
80 _ => unreachable!(),
81 }
82 }
83
84 pub fn then(&self) -> Result<Function<'js>> {
86 self.0.get(PredefinedAtom::Then)
87 }
88
89 pub fn catch(&self) -> Result<Function<'js>> {
91 self.0.get(PredefinedAtom::Catch)
92 }
93
94 pub fn result<T: FromJs<'js>>(&self) -> Option<Result<T>> {
100 match self.state() {
101 PromiseState::Pending => None,
102 PromiseState::Resolved => {
103 let v = unsafe { qjs::JS_PromiseResult(self.ctx().as_ptr(), self.as_js_value()) };
104 let v = unsafe { Value::from_js_value(self.ctx().clone(), v) };
105 Some(FromJs::from_js(self.ctx(), v))
106 }
107 PromiseState::Rejected => {
108 unsafe {
109 let v = qjs::JS_PromiseResult(self.ctx().as_ptr(), self.as_js_value());
110 qjs::JS_Throw(self.ctx().as_ptr(), v);
111 };
112 Some(Err(Error::Exception))
113 }
114 }
115 }
116
117 pub fn finish<T: FromJs<'js>>(&self) -> Result<T> {
125 loop {
126 if let Some(x) = self.result() {
127 return x;
128 }
129
130 if !self.ctx.execute_pending_job() {
131 return Err(Error::WouldBlock);
132 }
133 }
134 }
135
136 #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "futures")))]
138 #[cfg(feature = "futures")]
139 pub fn into_future<T>(self) -> PromiseFuture<'js, T>
140 where
141 T: FromJs<'js>,
142 {
143 PromiseFuture {
144 state: None,
145 promise: self,
146 _marker: PhantomData,
147 }
148 }
149}
150
151#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "futures")))]
153#[cfg(feature = "futures")]
154#[must_use = "futures do nothing unless you `.await` or poll them"]
155#[derive(Debug)]
156pub struct PromiseFuture<'js, T> {
157 state: Option<Rc<RefCell<Waker>>>,
158 promise: Promise<'js>,
159 _marker: PhantomData<T>,
160}
161
162#[cfg(feature = "futures")]
164impl<'js, T> Unpin for PromiseFuture<'js, T> {}
165
166#[cfg(feature = "futures")]
167impl<'js, T> Future for PromiseFuture<'js, T>
168where
169 T: FromJs<'js>,
170{
171 type Output = Result<T>;
172
173 fn poll(self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll<Self::Output> {
174 let this = self.get_mut();
175
176 if let Some(x) = this.promise.result() {
177 return Poll::Ready(x);
178 }
179
180 if this.state.is_none() {
181 let inner = Rc::new(RefCell::new(cx.waker().clone()));
182 this.state = Some(inner.clone());
183
184 let resolve = Function::new(this.promise.ctx.clone(), move || {
185 inner.borrow().wake_by_ref();
186 })?;
187
188 this.promise.then()?.call::<_, ()>((
189 This(this.promise.clone()),
190 resolve.clone(),
191 resolve,
192 ))?;
193 return Poll::Pending;
194 }
195
196 this.state
197 .as_ref()
198 .unwrap()
199 .borrow_mut()
200 .clone_from(cx.waker());
201
202 Poll::Pending
203 }
204}
205
206#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "futures")))]
208#[repr(transparent)]
209#[cfg(feature = "futures")]
210pub struct Promised<T>(pub T);
211
212#[cfg(feature = "futures")]
213impl<T> From<T> for Promised<T> {
214 fn from(future: T) -> Self {
215 Self(future)
216 }
217}
218
219#[cfg(feature = "futures")]
220impl<'js, T, R> IntoJs<'js> for Promised<T>
221where
222 T: Future<Output = R> + 'js,
223 R: IntoJs<'js> + 'js,
224{
225 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
226 Promise::wrap_future(ctx, self.0).map(|x| x.into_value())
227 }
228}
229
230#[derive(Debug, PartialEq, Clone, Hash, Eq)]
237pub struct MaybePromise<'js>(Value<'js>);
238
239impl<'js> FromJs<'js> for MaybePromise<'js> {
240 fn from_js(_ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
241 Ok(MaybePromise(value))
242 }
243}
244
245impl<'js> IntoJs<'js> for MaybePromise<'js> {
246 fn into_js(self, _ctx: &Ctx<'js>) -> Result<Value<'js>> {
247 Ok(self.0)
248 }
249}
250
251impl<'js> MaybePromise<'js> {
252 pub fn as_value(&self) -> &Value<'js> {
254 &self.0
255 }
256
257 pub fn into_value(self) -> Value<'js> {
259 self.0
260 }
261
262 pub fn from_value(value: Value<'js>) -> Self {
264 MaybePromise(value)
265 }
266
267 pub fn ctx(&self) -> &Ctx<'js> {
269 self.0.ctx()
270 }
271
272 pub fn state(&self) -> PromiseState {
275 if let Some(x) = self.0.as_promise() {
276 x.state()
277 } else {
278 PromiseState::Resolved
279 }
280 }
281
282 pub fn result<T: FromJs<'js>>(&self) -> Option<Result<T>> {
284 if let Some(x) = self.0.as_promise() {
285 x.result::<T>()
286 } else {
287 Some(T::from_js(self.0.ctx(), self.0.clone()))
288 }
289 }
290
291 pub fn finish<T: FromJs<'js>>(&self) -> Result<T> {
293 if let Some(x) = self.0.as_promise() {
294 x.finish::<T>()
295 } else {
296 T::from_js(self.0.ctx(), self.0.clone())
297 }
298 }
299
300 #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "futures")))]
304 #[cfg(feature = "futures")]
305 pub fn into_future<T: FromJs<'js>>(self) -> MaybePromiseFuture<'js, T> {
306 if self.0.is_promise() {
307 let fut = self.0.into_promise().unwrap().into_future();
308 MaybePromiseFuture(MaybePromiseFutureInner::Future(fut))
309 } else {
310 MaybePromiseFuture(MaybePromiseFutureInner::Ready(self.0))
311 }
312 }
313}
314
315#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "futures")))]
317#[cfg(feature = "futures")]
318#[must_use = "futures do nothing unless you `.await` or poll them"]
319#[derive(Debug)]
320pub struct MaybePromiseFuture<'js, T>(MaybePromiseFutureInner<'js, T>);
321
322#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "futures")))]
323#[cfg(feature = "futures")]
324#[derive(Debug)]
325enum MaybePromiseFutureInner<'js, T> {
326 Ready(Value<'js>),
327 Future(PromiseFuture<'js, T>),
328}
329
330#[cfg(feature = "futures")]
331impl<'js, T> Future for MaybePromiseFuture<'js, T>
332where
333 T: FromJs<'js>,
334{
335 type Output = Result<T>;
336
337 fn poll(self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll<Self::Output> {
338 match self.get_mut().0 {
339 MaybePromiseFutureInner::Ready(ref x) => Poll::Ready(T::from_js(x.ctx(), x.clone())),
340 MaybePromiseFutureInner::Future(ref mut x) => Pin::new(x).poll(cx),
341 }
342 }
343}
344
345#[cfg(test)]
346mod test {
347 use std::sync::atomic::{AtomicBool, Ordering};
348 #[cfg(feature = "futures")]
349 use std::time::Duration;
350
351 use super::Promise;
352 #[cfg(feature = "futures")]
353 use crate::{
354 async_with, function::Async, promise::Promised, AsyncContext, AsyncRuntime, CaughtError,
355 Result,
356 };
357 use crate::{
358 function::Func, prelude::This, promise::PromiseState, CatchResultExt, Context, Function,
359 Runtime,
360 };
361
362 #[cfg(feature = "futures")]
363 async fn set_timeout<'js>(cb: Function<'js>, number: f64) -> Result<()> {
364 tokio::time::sleep(Duration::from_secs_f64(number / 1000.0)).await;
365 cb.call::<_, ()>(())
366 }
367
368 #[cfg(feature = "futures")]
369 #[tokio::test]
370 async fn promise() {
371 let rt = AsyncRuntime::new().unwrap();
372 let ctx = AsyncContext::full(&rt).await.unwrap();
373
374 async_with!(ctx => |ctx| {
375 ctx.globals().set("setTimeout",Func::from(Async(set_timeout))).unwrap();
376
377 let func = ctx
378 .eval::<Function, _>(
379 r"
380 (function(){
381 return new Promise((resolve) => {
382 setTimeout(x => {
383 resolve(42)
384 },100)
385 })
386 })
387 ",
388 )
389 .catch(&ctx)
390 .unwrap();
391 let promise: Promise = func.call(()).unwrap();
392 assert_eq!(promise.into_future::<i32>().await.catch(&ctx).unwrap(), 42);
393
394 let func = ctx
395 .eval::<Function, _>(
396 r"
397 (function(){
398 return new Promise((_,reject) => {
399 setTimeout(x => {
400 reject(42)
401 },100)
402 })
403 })
404 ",
405 )
406 .catch(&ctx)
407 .unwrap();
408 let promise: Promise = func.call(()).unwrap();
409 let err = promise.into_future::<()>().await.catch(&ctx);
410 match err {
411 Err(CaughtError::Value(v)) => {
412 assert_eq!(v.as_int().unwrap(), 42)
413 }
414 _ => panic!(),
415 }
416 })
417 .await
418 }
419
420 #[cfg(feature = "futures")]
421 #[tokio::test]
422 async fn promised() {
423 use crate::Exception;
424
425 let rt = AsyncRuntime::new().unwrap();
426 let ctx = AsyncContext::full(&rt).await.unwrap();
427
428 async_with!(ctx => |ctx| {
429 let promised = Promised::from(async {
430 tokio::time::sleep(Duration::from_millis(100)).await;
431 42
432 });
433
434 let function = ctx.eval::<Function,_>(r"
435 (async function(v){
436 let val = await v;
437 if(val !== 42){
438 throw new Error('not correct value')
439 }
440 })
441 ").catch(&ctx).unwrap();
442
443 function.call::<_,Promise>((promised,)).unwrap().into_future::<()>().await.unwrap();
444
445 let ctx_clone = ctx.clone();
446 let promised = Promised::from(async move {
447 tokio::time::sleep(Duration::from_millis(100)).await;
448 Result::<()>::Err(Exception::throw_message(&ctx_clone, "some_message"))
449 });
450
451 let function = ctx.eval::<Function,_>(r"
452 (async function(v){
453 try{
454 await v;
455 }catch(e) {
456 if (e.message !== 'some_message'){
457 throw new Error('wrong error')
458 }
459 return
460 }
461 throw new Error('no error thrown')
462 })
463 ")
464 .catch(&ctx)
465 .unwrap();
466
467
468 function.call::<_,Promise>((promised,)).unwrap().into_future::<()>().await.unwrap()
469 })
470 .await
471 }
472
473 #[test]
474 fn promise_then() {
475 static DID_EXECUTE: AtomicBool = AtomicBool::new(false);
476
477 let rt = Runtime::new().unwrap();
478 let ctx = Context::full(&rt).unwrap();
479
480 ctx.with(|ctx| {
481 let (promise, resolve, _) = Promise::new(&ctx).unwrap();
482
483 let cb = Func::new(|s: String| {
484 assert_eq!(s, "FOO");
485 DID_EXECUTE.store(true, Ordering::SeqCst);
486 });
487
488 assert_eq!(promise.state(), PromiseState::Pending);
489
490 promise
491 .get::<_, Function>("then")
492 .catch(&ctx)
493 .unwrap()
494 .call::<_, ()>((This(promise.clone()), cb))
495 .catch(&ctx)
496 .unwrap();
497
498 resolve.call::<_, ()>(("FOO",)).unwrap();
499 assert_eq!(promise.state(), PromiseState::Resolved);
500
501 while ctx.execute_pending_job() {}
502
503 assert!(DID_EXECUTE.load(Ordering::SeqCst));
504 })
505 }
506}