use crate::facades::QuickJsRuntimeFacade;
use crate::quickjs_utils::errors;
use crate::quickjs_utils::promises::new_promise_q;
use crate::quickjs_utils::promises::PromiseRef;
use crate::quickjsrealmadapter::QuickJsRealmAdapter;
use crate::quickjsruntimeadapter::QuickJsRuntimeAdapter;
use crate::valueref::JSValueRef;
use hirofa_utils::auto_id_map::AutoIdMap;
use hirofa_utils::js_utils::JsError;
use std::cell::RefCell;
thread_local! {
static RESOLVING_PROMISES: RefCell<AutoIdMap<PromiseRef>> = RefCell::new(AutoIdMap::new());
}
pub fn new_resolving_promise<P, R, M>(
q_ctx: &QuickJsRealmAdapter,
producer: P,
mapper: M,
) -> Result<JSValueRef, JsError>
where
R: Send + 'static,
P: FnOnce() -> Result<R, String> + Send + 'static,
M: FnOnce(&QuickJsRealmAdapter, R) -> Result<JSValueRef, JsError> + Send + 'static,
{
let promise_ref = new_promise_q(q_ctx)?;
let return_ref = promise_ref.get_promise_obj_ref();
let id = RESOLVING_PROMISES.with(|map_rc| {
let map = &mut *map_rc.borrow_mut();
map.insert(promise_ref)
});
let rti_ref =
QuickJsRuntimeAdapter::do_with(|qjs_rt| qjs_rt.get_rti_ref().expect("invalid state"));
let ctx_id = q_ctx.id.clone();
QuickJsRuntimeFacade::add_helper_task(move || {
let produced_result = producer();
rti_ref.add_rt_task_to_event_loop_void(move |q_js_rt| {
if let Some(q_ctx) = q_js_rt.opt_context(ctx_id.as_str()) {
let prom_ref = RESOLVING_PROMISES.with(|map_rc| {
let map = &mut *map_rc.borrow_mut();
map.remove(&id)
});
match produced_result {
Ok(ok_res) => {
let raw_res = mapper(q_ctx, ok_res);
match raw_res {
Ok(val_ref) => {
prom_ref
.resolve_q(q_ctx, val_ref)
.expect("prom resolution failed");
}
Err(err) => {
let err_ref = unsafe {
errors::new_error(
q_ctx.context,
err.get_name(),
err.get_message(),
err.get_stack(),
)
}
.expect("could not create str");
prom_ref
.reject_q(q_ctx, err_ref)
.expect("prom rejection failed");
}
}
}
Err(err) => {
let err_ref =
unsafe { errors::new_error(q_ctx.context, "Error", err.as_str(), "") }
.expect("could not create str");
prom_ref
.reject_q(q_ctx, err_ref)
.expect("prom rejection failed");
}
}
} else {
log::error!("resolving_promise failed, context was dropped: {}", ctx_id);
}
});
});
Ok(return_ref)
}
#[cfg(test)]
pub mod tests {
use crate::facades::tests::init_test_rt;
use crate::quickjs_utils;
use crate::quickjs_utils::{functions, objects, primitives};
use crate::runtimefacade_utils::promises;
use crate::runtimefacade_utils::promises::RESOLVING_PROMISES;
use hirofa_utils::js_utils::Script;
use std::time::Duration;
#[test]
fn test_resolving_prom() {
let rt = init_test_rt();
rt.exe_rt_task_in_event_loop(move |q_js_rt| {
let q_ctx = q_js_rt.get_main_context();
let func_ref = functions::new_function_q(
q_ctx,
"asyncTest",
move |q_ctx, _this_ref, _args| {
promises::new_resolving_promise(
q_ctx,
|| {
std::thread::sleep(Duration::from_millis(5));
Ok(135)
},
|_q_ctx, res| Ok(primitives::from_i32(res)),
)
},
1,
)
.ok()
.expect("could not create func");
assert_eq!(1, func_ref.get_ref_count());
let global_ref = quickjs_utils::get_global_q(q_ctx);
let i = global_ref.get_ref_count();
objects::set_property_q(q_ctx, &global_ref, "asyncTest", &func_ref)
.ok()
.expect("could not set prop");
assert_eq!(i, global_ref.get_ref_count());
assert_eq!(2, func_ref.get_ref_count());
});
log::trace!("running gc after resolving promise init");
rt.eval_sync(Script::new(
"test_async.es",
"console.log('async test');\n
let p = this.asyncTest(123); \n
console.log('p instanceof Promise = ' + p instanceof Promise);\n
p.then((res) => {\n
console.log('p resolved to ' + res);\n
}).catch((err) => {\n
console.log('p rejected to ' + err);\n
});
",
))
.ok()
.expect("script failed");
rt.gc_sync();
std::thread::sleep(Duration::from_secs(10));
assert!(RESOLVING_PROMISES.with(|rc| { (*rc.borrow()).is_empty() }))
}
#[test]
fn test_simple_prom() {
let rt = init_test_rt();
rt.exe_rt_task_in_event_loop(|q_js_rt| {
let q_ctx = q_js_rt.get_main_context();
q_ctx.eval(Script::new(
"test_simple_prom.es",
"this.test = function(){return new Promise((resolve, reject) => {resolve('abc');}).then((a) => {return(a.toUpperCase());})}",
)).expect("p1");
q_js_rt.run_pending_jobs_if_any();
let global = quickjs_utils::get_global_q(q_ctx);
let e_res = functions::invoke_member_function_q(q_ctx, &global, "test", vec![quickjs_utils::new_null_ref()]);
if e_res.is_err() {
panic!("{}", e_res.err().unwrap());
}
let _p_ref = e_res.ok().unwrap();
q_js_rt.run_pending_jobs_if_any();
});
std::thread::sleep(Duration::from_secs(1));
}
}