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 match reason_str_res {
278 Ok(reason_str) => {
279 log::error!(
280 "[{}] unhandled promise rejection, reason: {}{}",
281 realm_id,
282 reason_str,
283 stack
284 );
285 }
286 Err(e) => {
287 log::error!(
288 "[{}] unhandled promise rejection, could not get reason: {}{}",
289 realm_id,
290 e,
291 stack
292 );
293 }
294 }
295 });
296 }
297}
298
299#[cfg(test)]
300pub mod tests {
301 use crate::builder::QuickJsRuntimeBuilder;
302 use crate::facades::tests::init_test_rt;
303 use crate::jsutils::Script;
304 use crate::quickjs_utils::promises::{add_promise_reactions_q, is_promise_q, new_promise_q};
305 use crate::quickjs_utils::{functions, new_null_ref, primitives};
306 use crate::quickjsruntimeadapter::QuickJsRuntimeAdapter;
307 use crate::values::JsValueFacade;
308 use futures::executor::block_on;
309 use std::time::Duration;
310
311 #[test]
312 fn test_instance_of_prom() {
313 log::info!("> test_instance_of_prom");
314
315 let rt = init_test_rt();
316 let io = rt.exe_rt_task_in_event_loop(|q_js_rt| {
317 let q_ctx = q_js_rt.get_main_realm();
318 let res = q_ctx.eval(Script::new(
319 "test_instance_of_prom.es",
320 "(new Promise((res, rej) => {}));",
321 ));
322 match res {
323 Ok(v) => {
324 log::info!("checking if instance_of prom");
325
326 is_promise_q(q_ctx, &v)
327 && is_promise_q(q_ctx, &v)
328 && is_promise_q(q_ctx, &v)
329 && is_promise_q(q_ctx, &v)
330 && is_promise_q(q_ctx, &v)
331 }
332 Err(e) => {
333 log::error!("err testing instance_of prom: {}", e);
334 false
335 }
336 }
337 });
338 assert!(io);
339
340 log::info!("< test_instance_of_prom");
341 std::thread::sleep(Duration::from_secs(1));
342 }
343
344 #[test]
345 fn new_prom() {
346 log::info!("> new_prom");
347
348 let rt = init_test_rt();
349 rt.exe_rt_task_in_event_loop(|q_js_rt| {
350 let q_ctx = q_js_rt.get_main_realm();
351 let func_ref = q_ctx
352 .eval(Script::new(
353 "new_prom.es",
354 "(function(p){p.then((res) => {console.log('prom resolved to ' + res);});});",
355 ))
356 .ok()
357 .unwrap();
358
359 let prom = new_promise_q(q_ctx).ok().unwrap();
360
361 let res =
362 functions::call_function_q(q_ctx, &func_ref, &[prom.get_promise_obj_ref()], None);
363 if res.is_err() {
364 panic!("func call failed: {}", res.err().unwrap());
365 }
366
367 unsafe {
368 prom.resolve(q_ctx.context, primitives::from_i32(743))
369 .expect("resolve failed");
370 }
371 });
372 std::thread::sleep(Duration::from_secs(1));
373
374 log::info!("< new_prom");
375 }
376
377 #[test]
378 fn new_prom2() {
379 log::info!("> new_prom2");
380
381 let rt = init_test_rt();
382 rt.exe_rt_task_in_event_loop(|q_js_rt| {
383 let q_ctx = q_js_rt.get_main_realm();
384 let func_ref = q_ctx
385 .eval(Script::new(
386 "new_prom.es",
387 "(function(p){p.catch((res) => {console.log('prom rejected to ' + res);});});",
388 ))
389 .ok()
390 .unwrap();
391
392 let prom = new_promise_q(q_ctx).ok().unwrap();
393
394 let res =
395 functions::call_function_q(q_ctx, &func_ref, &[prom.get_promise_obj_ref()], None);
396 if res.is_err() {
397 panic!("func call failed: {}", res.err().unwrap());
398 }
399
400 unsafe {
401 prom.reject(q_ctx.context, primitives::from_i32(130))
402 .expect("reject failed");
403 }
404 });
405 std::thread::sleep(Duration::from_secs(1));
406
407 log::info!("< new_prom2");
408 }
409
410 #[test]
411 fn test_promise_reactions() {
412 log::info!("> test_promise_reactions");
413
414 let rt = init_test_rt();
415 rt.exe_rt_task_in_event_loop(|q_js_rt| {
416 let q_ctx = q_js_rt.get_main_realm();
417 let prom_ref = q_ctx
418 .eval(Script::new(
419 "test_promise_reactions.es",
420 "(new Promise(function(resolve, reject) {resolve(364);}));",
421 ))
422 .expect("script failed");
423
424 let then_cb = functions::new_function_q(
425 q_ctx,
426 "testThen",
427 |_q_ctx, _this, args| {
428 let res = primitives::to_i32(args.first().unwrap()).ok().unwrap();
429 log::trace!("prom resolved with: {}", res);
430 Ok(new_null_ref())
431 },
432 1,
433 )
434 .expect("could not create cb");
435 let finally_cb = functions::new_function_q(
436 q_ctx,
437 "testThen",
438 |_q_ctx, _this, _args| {
439 log::trace!("prom finalized");
440
441 Ok(new_null_ref())
442 },
443 1,
444 )
445 .expect("could not create cb");
446
447 add_promise_reactions_q(q_ctx, &prom_ref, Some(then_cb), None, Some(finally_cb))
448 .expect("could not add promise reactions");
449 });
450 std::thread::sleep(Duration::from_secs(1));
451
452 log::info!("< test_promise_reactions");
453 }
454
455 #[tokio::test]
456 async fn test_promise_async() {
457 let rt = init_test_rt();
458 let jsvf = rt
459 .eval(
460 None,
461 Script::new("test_prom_async.js", "Promise.resolve(123)"),
462 )
463 .await
464 .expect("script failed");
465 if let JsValueFacade::JsPromise { cached_promise } = jsvf {
466 let res = cached_promise
467 .get_promise_result()
468 .await
469 .expect("promise resolve send code stuf exploded");
470 match res {
471 Ok(prom_res) => {
472 if prom_res.is_i32() {
473 assert_eq!(prom_res.get_i32(), 123);
474 } else {
475 panic!("promise did not resolve to an i32.. well that was unexpected!");
476 }
477 }
478 Err(e) => {
479 panic!("prom was rejected: {}", e.stringify())
480 }
481 }
482 }
483 }
484 #[test]
485 fn test_promise_nested() {
486 log::info!("> test_promise_nested");
487
488 let rt = init_test_rt();
489
490 let mut jsvf_res = rt.exe_task_in_event_loop(|| {
491 QuickJsRuntimeAdapter::create_context("test").expect("create ctx failed");
492 QuickJsRuntimeAdapter::do_with(|q_js_rt| {
493 let q_ctx = q_js_rt.get_context("test");
494
495 let script = "(new Promise((resolve, reject) => {resolve({a: 7});}).then((obj) => {return {b: obj.a * 5}}));";
496 let esvf_res = q_ctx
497 .eval(Script::new("test_promise_nested.es", script))
498 .expect("script failed");
499
500 q_ctx.to_js_value_facade(&esvf_res).expect("poof")
501
502 })
503 });
504
505 while jsvf_res.is_js_promise() {
506 match jsvf_res {
507 JsValueFacade::JsPromise { cached_promise } => {
508 jsvf_res = cached_promise
509 .get_promise_result_sync()
510 .expect("prom timed out")
511 .expect("prom was rejected");
512 }
513 _ => {}
514 }
515 }
516
517 assert!(jsvf_res.is_js_object());
518
519 match jsvf_res {
520 JsValueFacade::JsObject { cached_object } => {
521 let obj = cached_object.get_object_sync().expect("esvf to map failed");
522 let b = obj.get("b").expect("got no b");
523 assert!(b.is_i32());
524 let i = b.get_i32();
525 assert_eq!(i, 5 * 7);
526 }
527 _ => {}
528 }
529
530 rt.exe_task_in_event_loop(|| {
531 let _ = QuickJsRuntimeAdapter::remove_context("test");
532 })
533 }
534
535 #[test]
536 fn test_to_string_err() {
537 let rt = QuickJsRuntimeBuilder::new().build();
538
539 let res = block_on(rt.eval(
540 None,
541 Script::new(
542 "test_test_to_string_err.js",
543 r#"
544 (async () => {
545 throw Error("poof");
546 })();
547 "#,
548 ),
549 ));
550 match res {
551 Ok(val) => {
552 if let JsValueFacade::JsPromise { cached_promise } = val {
553 let prom_res =
554 block_on(cached_promise.get_promise_result()).expect("promise timed out");
555 match prom_res {
556 Ok(v) => {
557 panic!("promise unexpectedly resolved to val: {:?}", v);
558 }
559 Err(ev) => {
560 println!("prom resolved to error: {ev:?}");
561 }
562 }
563 } else {
564 panic!("func did not return a promise");
565 }
566 }
567 Err(e) => {
568 panic!("scrtip failed {}", e)
569 }
570 }
571 }
572}