1use std::future::Future;
4use std::sync::OnceLock;
5
6use crate::utils::primordials::{BasePrimordials, Primordial};
7use rquickjs::{atom::PredefinedAtom, CatchResultExt, CaughtError, Ctx, Object, Result};
8use tokio::sync::oneshot::{self, Receiver};
9use tracing::trace;
10
11#[allow(clippy::type_complexity)]
12static ERROR_HANDLER: OnceLock<Box<dyn for<'js> Fn(&Ctx<'js>, CaughtError<'js>) + Sync + Send>> =
13 OnceLock::new();
14
15pub trait CtxExtension<'js> {
16 fn spawn_exit<F, R>(&self, future: F) -> Result<Receiver<R>>
19 where
20 F: Future<Output = Result<R>> + 'js,
21 R: 'js;
22
23 fn spawn_exit_simple<F>(&self, future: F)
24 where
25 F: Future<Output = Result<()>> + 'js;
26}
27
28impl<'js> CtxExtension<'js> for Ctx<'js> {
29 fn spawn_exit<F, R>(&self, future: F) -> Result<Receiver<R>>
30 where
31 F: Future<Output = Result<R>> + 'js,
32 R: 'js,
33 {
34 let ctx = self.clone();
35
36 let primordials = BasePrimordials::get(self)?;
37 let type_error: Object = primordials.constructor_type_error.construct(())?;
38 let stack: Option<String> = type_error.get(PredefinedAtom::Stack).ok();
39
40 let (join_channel_tx, join_channel_rx) = oneshot::channel();
41
42 self.spawn(async move {
43 match future.await.catch(&ctx) {
44 Ok(res) => {
45 let _ = join_channel_tx.send(res);
47 },
48 Err(err) => handle_spawn_error(&ctx, err, stack),
49 }
50 });
51 Ok(join_channel_rx)
52 }
53
54 fn spawn_exit_simple<F>(&self, future: F)
56 where
57 F: Future<Output = Result<()>> + 'js,
58 {
59 let ctx = self.clone();
60 self.spawn(async move {
61 if let Err(err) = future.await.catch(&ctx) {
62 handle_spawn_error(&ctx, err, None)
63 }
64 });
65 }
66}
67
68fn handle_spawn_error<'js>(ctx: &Ctx<'js>, err: CaughtError<'js>, stack: Option<String>) {
69 let error_handler = if let Some(handler) = ERROR_HANDLER.get() {
70 handler
71 } else {
72 trace!("Future error: {:?}", err);
73 return;
74 };
75 if let CaughtError::Exception(err) = err {
76 if err.stack().is_none() {
77 if let Some(stack) = stack {
78 err.set(PredefinedAtom::Stack, stack).unwrap();
79 }
80 }
81 error_handler(ctx, CaughtError::Exception(err));
82 } else {
83 error_handler(ctx, err);
84 }
85}
86
87pub fn set_spawn_error_handler<F>(handler: F)
88where
89 F: for<'js> Fn(&Ctx<'js>, CaughtError<'js>) + Sync + Send + 'static,
90{
91 _ = ERROR_HANDLER.set(Box::new(handler));
92}