1use crate::jsutils::JsError;
2use crate::quickjs_utils;
3#[cfg(feature = "bellard")]
4use crate::quickjs_utils::class_ids::JS_CLASS_PROMISE;
5use crate::quickjs_utils::errors::get_stack;
6use crate::quickjs_utils::functions;
7use crate::quickjsrealmadapter::QuickJsRealmAdapter;
8use crate::quickjsruntimeadapter::QuickJsRuntimeAdapter;
9use crate::quickjsvalueadapter::QuickJsValueAdapter;
10use libquickjs_sys as q;
11#[cfg(feature = "bellard")]
12use libquickjs_sys::JS_GetClassID;
13#[cfg(feature = "quickjs-ng")]
14use libquickjs_sys::JS_IsPromise;
15
16pub fn is_promise_q(context: &QuickJsRealmAdapter, obj_ref: &QuickJsValueAdapter) -> bool {
17 unsafe { is_promise(context.context, obj_ref) }
18}
19
20#[allow(dead_code)]
21#[allow(unused_variables)]
24pub unsafe fn is_promise(ctx: *mut q::JSContext, obj: &QuickJsValueAdapter) -> bool {
25 #[cfg(feature = "bellard")]
26 {
27 JS_GetClassID(*obj.borrow_value()) == JS_CLASS_PROMISE
28 }
29 #[cfg(feature = "quickjs-ng")]
30 {
31 JS_IsPromise(*obj.borrow_value())
32 }
33}
34
35pub struct QuickJsPromiseAdapter {
36 promise_obj_ref: QuickJsValueAdapter,
37 reject_function_obj_ref: QuickJsValueAdapter,
38 resolve_function_obj_ref: QuickJsValueAdapter,
39}
40#[allow(dead_code)]
41impl QuickJsPromiseAdapter {
42 pub fn get_promise_obj_ref(&self) -> QuickJsValueAdapter {
43 self.promise_obj_ref.clone()
44 }
45
46 pub fn resolve_q(
47 &self,
48 q_ctx: &QuickJsRealmAdapter,
49 value: QuickJsValueAdapter,
50 ) -> Result<(), JsError> {
51 unsafe { self.resolve(q_ctx.context, value) }
52 }
53 pub unsafe fn resolve(
56 &self,
57 context: *mut q::JSContext,
58 value: QuickJsValueAdapter,
59 ) -> Result<(), JsError> {
60 log::trace!("PromiseRef.resolve()");
61 crate::quickjs_utils::functions::call_function(
62 context,
63 &self.resolve_function_obj_ref,
64 &[value],
65 None,
66 )?;
67 Ok(())
68 }
69 pub fn reject_q(
70 &self,
71 q_ctx: &QuickJsRealmAdapter,
72 value: QuickJsValueAdapter,
73 ) -> Result<(), JsError> {
74 unsafe { self.reject(q_ctx.context, value) }
75 }
76 pub unsafe fn reject(
79 &self,
80 context: *mut q::JSContext,
81 value: QuickJsValueAdapter,
82 ) -> Result<(), JsError> {
83 log::trace!("PromiseRef.reject()");
84 crate::quickjs_utils::functions::call_function(
85 context,
86 &self.reject_function_obj_ref,
87 &[value],
88 None,
89 )?;
90 Ok(())
91 }
92}
93
94impl Clone for QuickJsPromiseAdapter {
95 fn clone(&self) -> Self {
96 Self {
97 promise_obj_ref: self.promise_obj_ref.clone(),
98 reject_function_obj_ref: self.reject_function_obj_ref.clone(),
99 resolve_function_obj_ref: self.resolve_function_obj_ref.clone(),
100 }
101 }
102}
103
104impl QuickJsPromiseAdapter {
105 pub fn js_promise_resolve(
106 &self,
107 context: &QuickJsRealmAdapter,
108 resolution: &QuickJsValueAdapter,
109 ) -> Result<(), JsError> {
110 self.resolve_q(context, resolution.clone())
111 }
112
113 pub fn js_promise_reject(
114 &self,
115 context: &QuickJsRealmAdapter,
116 rejection: &QuickJsValueAdapter,
117 ) -> Result<(), JsError> {
118 self.reject_q(context, rejection.clone())
119 }
120
121 pub fn js_promise_get_value(&self, _realm: &QuickJsRealmAdapter) -> QuickJsValueAdapter {
122 self.promise_obj_ref.clone()
123 }
124}
125
126pub fn new_promise_q(q_ctx: &QuickJsRealmAdapter) -> Result<QuickJsPromiseAdapter, JsError> {
127 unsafe { new_promise(q_ctx.context) }
128}
129
130pub unsafe fn new_promise(context: *mut q::JSContext) -> Result<QuickJsPromiseAdapter, JsError> {
135 log::trace!("promises::new_promise()");
136
137 let mut promise_resolution_functions = [quickjs_utils::new_null(), quickjs_utils::new_null()];
138
139 let prom_val = q::JS_NewPromiseCapability(context, promise_resolution_functions.as_mut_ptr());
140
141 let resolve_func_val = *promise_resolution_functions.first().unwrap();
142 let reject_func_val = *promise_resolution_functions.get(1).unwrap();
143
144 let resolve_function_obj_ref = QuickJsValueAdapter::new(
145 context,
146 resolve_func_val,
147 false,
148 true,
149 "promises::new_promise resolve_func_val",
150 );
151 let reject_function_obj_ref = QuickJsValueAdapter::new(
152 context,
153 reject_func_val,
154 false,
155 true,
156 "promises::new_promise reject_func_val",
157 );
158 debug_assert!(functions::is_function(context, &resolve_function_obj_ref));
159 debug_assert!(functions::is_function(context, &reject_function_obj_ref));
160
161 let promise_obj_ref = QuickJsValueAdapter::new(
162 context,
163 prom_val,
164 false,
165 true,
166 "promises::new_promise prom_val",
167 );
168
169 #[cfg(feature = "bellard")]
170 debug_assert_eq!(resolve_function_obj_ref.get_ref_count(), 1);
171 #[cfg(feature = "bellard")]
172 debug_assert_eq!(reject_function_obj_ref.get_ref_count(), 1);
173 #[cfg(feature = "bellard")]
174 debug_assert_eq!(promise_obj_ref.get_ref_count(), 3);
175
176 Ok(QuickJsPromiseAdapter {
177 promise_obj_ref,
178 reject_function_obj_ref,
179 resolve_function_obj_ref,
180 })
181}
182
183pub(crate) fn init_promise_rejection_tracker(q_js_rt: &QuickJsRuntimeAdapter) {
184 let tracker: q::JSHostPromiseRejectionTracker = Some(promise_rejection_tracker);
185
186 unsafe {
187 q::JS_SetHostPromiseRejectionTracker(q_js_rt.runtime, tracker, std::ptr::null_mut());
188 }
189}
190
191pub fn add_promise_reactions_q(
192 context: &QuickJsRealmAdapter,
193 promise_obj_ref: &QuickJsValueAdapter,
194 then_func_obj_ref_opt: Option<QuickJsValueAdapter>,
195 catch_func_obj_ref_opt: Option<QuickJsValueAdapter>,
196 finally_func_obj_ref_opt: Option<QuickJsValueAdapter>,
197) -> Result<(), JsError> {
198 unsafe {
199 add_promise_reactions(
200 context.context,
201 promise_obj_ref,
202 then_func_obj_ref_opt,
203 catch_func_obj_ref_opt,
204 finally_func_obj_ref_opt,
205 )
206 }
207}
208
209#[allow(dead_code)]
210pub unsafe fn add_promise_reactions(
213 context: *mut q::JSContext,
214 promise_obj_ref: &QuickJsValueAdapter,
215 then_func_obj_ref_opt: Option<QuickJsValueAdapter>,
216 catch_func_obj_ref_opt: Option<QuickJsValueAdapter>,
217 finally_func_obj_ref_opt: Option<QuickJsValueAdapter>,
218) -> Result<(), JsError> {
219 debug_assert!(is_promise(context, promise_obj_ref));
220
221 if let Some(then_func_obj_ref) = then_func_obj_ref_opt {
222 functions::invoke_member_function(context, promise_obj_ref, "then", &[then_func_obj_ref])?;
223 }
224 if let Some(catch_func_obj_ref) = catch_func_obj_ref_opt {
225 functions::invoke_member_function(
226 context,
227 promise_obj_ref,
228 "catch",
229 &[catch_func_obj_ref],
230 )?;
231 }
232 if let Some(finally_func_obj_ref) = finally_func_obj_ref_opt {
233 functions::invoke_member_function(
234 context,
235 promise_obj_ref,
236 "finally",
237 &[finally_func_obj_ref],
238 )?;
239 }
240
241 Ok(())
242}
243
244unsafe extern "C" fn promise_rejection_tracker(
245 ctx: *mut q::JSContext,
246 _promise: q::JSValue,
247 reason: q::JSValue,
248 #[cfg(feature = "bellard")] is_handled: ::std::os::raw::c_int,
249 #[cfg(feature = "quickjs-ng")] is_handled: bool,
250
251 _opaque: *mut ::std::os::raw::c_void,
252) {
253 #[cfg(feature = "bellard")]
254 let handled = is_handled != 0;
255 #[cfg(feature = "quickjs-ng")]
256 let handled = is_handled;
257
258 if !handled {
259 let reason_ref = QuickJsValueAdapter::new(
260 ctx,
261 reason,
262 false,
263 false,
264 "promises::promise_rejection_tracker reason",
265 );
266 let reason_str_res = functions::call_to_string(ctx, &reason_ref);
267 QuickJsRuntimeAdapter::do_with(|rt| {
268 let realm = rt.get_quickjs_context(ctx);
269 let realm_id = realm.get_realm_id();
270 let stack = match get_stack(realm) {
271 Ok(s) => match s.to_string() {
272 Ok(s) => s,
273 Err(_) => "".to_string(),
274 },
275 Err(_) => "".to_string(),
276 };
277 #[cfg(feature = "typescript")]
278 let stack = crate::typescript::unmap_stack_trace(stack.as_str());
279
280 match reason_str_res {
281 Ok(reason_str) => {
282 log::error!(
283 "[{}] unhandled promise rejection, reason: {}\nRejection stack:\n{}",
284 realm_id,
285 reason_str,
286 stack
287 );
288 }
289 Err(e) => {
290 log::error!(
291 "[{}] unhandled promise rejection, could not get reason: {}\nRejection stack:\n{}",
292 realm_id,
293 e,
294 stack
295 );
296 }
297 }
298 });
299 }
300}
301
302#[cfg(test)]
303pub mod tests {
304 use crate::builder::QuickJsRuntimeBuilder;
305 use crate::facades::tests::init_test_rt;
306 use crate::jsutils::Script;
307 use crate::quickjs_utils::promises::{add_promise_reactions_q, is_promise_q, new_promise_q};
308 use crate::quickjs_utils::{functions, new_null_ref, primitives};
309 use crate::quickjsruntimeadapter::QuickJsRuntimeAdapter;
310 use crate::values::JsValueFacade;
311 use futures::executor::block_on;
312 use std::time::Duration;
313
314 #[test]
315 fn test_instance_of_prom() {
316 log::info!("> test_instance_of_prom");
317
318 let rt = init_test_rt();
319 let io = rt.exe_rt_task_in_event_loop(|q_js_rt| {
320 let q_ctx = q_js_rt.get_main_realm();
321 let res = q_ctx.eval(Script::new(
322 "test_instance_of_prom.es",
323 "(new Promise((res, rej) => {}));",
324 ));
325 match res {
326 Ok(v) => {
327 log::info!("checking if instance_of prom");
328
329 is_promise_q(q_ctx, &v)
330 && is_promise_q(q_ctx, &v)
331 && is_promise_q(q_ctx, &v)
332 && is_promise_q(q_ctx, &v)
333 && is_promise_q(q_ctx, &v)
334 }
335 Err(e) => {
336 log::error!("err testing instance_of prom: {}", e);
337 false
338 }
339 }
340 });
341 assert!(io);
342
343 log::info!("< test_instance_of_prom");
344 std::thread::sleep(Duration::from_secs(1));
345 }
346
347 #[test]
348 fn new_prom() {
349 log::info!("> new_prom");
350
351 let rt = init_test_rt();
352 rt.exe_rt_task_in_event_loop(|q_js_rt| {
353 let q_ctx = q_js_rt.get_main_realm();
354 let func_ref = q_ctx
355 .eval(Script::new(
356 "new_prom.es",
357 "(function(p){p.then((res) => {console.log('prom resolved to ' + res);});});",
358 ))
359 .ok()
360 .unwrap();
361
362 let prom = new_promise_q(q_ctx).ok().unwrap();
363
364 let res =
365 functions::call_function_q(q_ctx, &func_ref, &[prom.get_promise_obj_ref()], None);
366 if res.is_err() {
367 panic!("func call failed: {}", res.err().unwrap());
368 }
369
370 unsafe {
371 prom.resolve(q_ctx.context, primitives::from_i32(743))
372 .expect("resolve failed");
373 }
374 });
375 std::thread::sleep(Duration::from_secs(1));
376
377 log::info!("< new_prom");
378 }
379
380 #[test]
381 fn new_prom2() {
382 log::info!("> new_prom2");
383
384 let rt = init_test_rt();
385 rt.exe_rt_task_in_event_loop(|q_js_rt| {
386 let q_ctx = q_js_rt.get_main_realm();
387 let func_ref = q_ctx
388 .eval(Script::new(
389 "new_prom.es",
390 "(function(p){p.catch((res) => {console.log('prom rejected to ' + res);});});",
391 ))
392 .ok()
393 .unwrap();
394
395 let prom = new_promise_q(q_ctx).ok().unwrap();
396
397 let res =
398 functions::call_function_q(q_ctx, &func_ref, &[prom.get_promise_obj_ref()], None);
399 if res.is_err() {
400 panic!("func call failed: {}", res.err().unwrap());
401 }
402
403 unsafe {
404 prom.reject(q_ctx.context, primitives::from_i32(130))
405 .expect("reject failed");
406 }
407 });
408 std::thread::sleep(Duration::from_secs(1));
409
410 log::info!("< new_prom2");
411 }
412
413 #[test]
414 fn test_promise_reactions() {
415 log::info!("> test_promise_reactions");
416
417 let rt = init_test_rt();
418 rt.exe_rt_task_in_event_loop(|q_js_rt| {
419 let q_ctx = q_js_rt.get_main_realm();
420 let prom_ref = q_ctx
421 .eval(Script::new(
422 "test_promise_reactions.es",
423 "(new Promise(function(resolve, reject) {resolve(364);}));",
424 ))
425 .expect("script failed");
426
427 let then_cb = functions::new_function_q(
428 q_ctx,
429 "testThen",
430 |_q_ctx, _this, args| {
431 let res = primitives::to_i32(args.first().unwrap()).ok().unwrap();
432 log::trace!("prom resolved with: {}", res);
433 Ok(new_null_ref())
434 },
435 1,
436 )
437 .expect("could not create cb");
438 let finally_cb = functions::new_function_q(
439 q_ctx,
440 "testThen",
441 |_q_ctx, _this, _args| {
442 log::trace!("prom finalized");
443
444 Ok(new_null_ref())
445 },
446 1,
447 )
448 .expect("could not create cb");
449
450 add_promise_reactions_q(q_ctx, &prom_ref, Some(then_cb), None, Some(finally_cb))
451 .expect("could not add promise reactions");
452 });
453 std::thread::sleep(Duration::from_secs(1));
454
455 log::info!("< test_promise_reactions");
456 }
457
458 #[tokio::test]
459 async fn test_promise_async() {
460 let rt = init_test_rt();
461 let jsvf = rt
462 .eval(
463 None,
464 Script::new("test_prom_async.js", "Promise.resolve(123)"),
465 )
466 .await
467 .expect("script failed");
468 if let JsValueFacade::JsPromise { cached_promise } = jsvf {
469 let res = cached_promise
470 .get_promise_result()
471 .await
472 .expect("promise resolve send code stuf exploded");
473 match res {
474 Ok(prom_res) => {
475 if prom_res.is_i32() {
476 assert_eq!(prom_res.get_i32(), 123);
477 } else {
478 panic!("promise did not resolve to an i32.. well that was unexpected!");
479 }
480 }
481 Err(e) => {
482 panic!("prom was rejected: {}", e.stringify())
483 }
484 }
485 }
486 }
487 #[test]
488 fn test_promise_nested() {
489 log::info!("> test_promise_nested");
490
491 let rt = init_test_rt();
492
493 let mut jsvf_res = rt.exe_task_in_event_loop(|| {
494 QuickJsRuntimeAdapter::create_context("test").expect("create ctx failed");
495 QuickJsRuntimeAdapter::do_with(|q_js_rt| {
496 let q_ctx = q_js_rt.get_context("test");
497
498 let script = "(new Promise((resolve, reject) => {resolve({a: 7});}).then((obj) => {return {b: obj.a * 5}}));";
499 let esvf_res = q_ctx
500 .eval(Script::new("test_promise_nested.es", script))
501 .expect("script failed");
502
503 q_ctx.to_js_value_facade(&esvf_res).expect("poof")
504
505 })
506 });
507
508 while jsvf_res.is_js_promise() {
509 match jsvf_res {
510 JsValueFacade::JsPromise { cached_promise } => {
511 jsvf_res = cached_promise
512 .get_promise_result_sync()
513 .expect("prom timed out")
514 .expect("prom was rejected");
515 }
516 _ => {}
517 }
518 }
519
520 assert!(jsvf_res.is_js_object());
521
522 match jsvf_res {
523 JsValueFacade::JsObject { cached_object } => {
524 let obj = cached_object.get_object_sync().expect("esvf to map failed");
525 let b = obj.get("b").expect("got no b");
526 assert!(b.is_i32());
527 let i = b.get_i32();
528 assert_eq!(i, 5 * 7);
529 }
530 _ => {}
531 }
532
533 rt.exe_task_in_event_loop(|| {
534 let _ = QuickJsRuntimeAdapter::remove_context("test");
535 })
536 }
537
538 #[test]
539 fn test_to_string_err() {
540 let rt = QuickJsRuntimeBuilder::new().build();
541
542 let res = block_on(rt.eval(
543 None,
544 Script::new(
545 "test_test_to_string_err.js",
546 r#"
547 (async () => {
548 throw Error("poof");
549 })();
550 "#,
551 ),
552 ));
553 match res {
554 Ok(val) => {
555 if let JsValueFacade::JsPromise { cached_promise } = val {
556 let prom_res =
557 block_on(cached_promise.get_promise_result()).expect("promise timed out");
558 match prom_res {
559 Ok(v) => {
560 panic!("promise unexpectedly resolved to val: {:?}", v);
561 }
562 Err(ev) => {
563 println!("prom resolved to error: {ev:?}");
564 }
565 }
566 } else {
567 panic!("func did not return a promise");
568 }
569 }
570 Err(e) => {
571 panic!("scrtip failed {}", e)
572 }
573 }
574 }
575}