napi/bindgen_runtime/js_values/
promise_raw.rs

1use std::ffi::c_void;
2use std::marker::PhantomData;
3use std::ptr;
4
5#[cfg(all(feature = "napi4", feature = "tokio_rt"))]
6use crate::bindgen_runtime::Promise;
7use crate::{
8  bindgen_prelude::{
9    FromNapiValue, JsObjectValue, Result, ToNapiValue, TypeName, ValidateNapiValue,
10  },
11  check_status, sys, Env, Error, JsValue, Value, ValueType,
12};
13
14#[derive(Clone, Copy)]
15pub struct PromiseRaw<'env, T> {
16  pub(crate) inner: sys::napi_value,
17  env: sys::napi_env,
18  _phantom: &'env PhantomData<T>,
19}
20
21impl<'env, T> JsValue<'env> for PromiseRaw<'env, T> {
22  fn value(&self) -> Value {
23    Value {
24      env: self.env,
25      value: self.inner,
26      value_type: ValueType::Object,
27    }
28  }
29}
30
31impl<'env, T> JsObjectValue<'env> for PromiseRaw<'env, T> {}
32
33impl<T> TypeName for PromiseRaw<'_, T> {
34  fn type_name() -> &'static str {
35    "Promise"
36  }
37
38  fn value_type() -> crate::ValueType {
39    crate::ValueType::Object
40  }
41}
42
43impl<T> ValidateNapiValue for PromiseRaw<'_, T> {
44  unsafe fn validate(
45    env: napi_sys::napi_env,
46    napi_val: napi_sys::napi_value,
47  ) -> Result<napi_sys::napi_value> {
48    validate_promise(env, napi_val)
49  }
50}
51
52impl<T> FromNapiValue for PromiseRaw<'_, T> {
53  unsafe fn from_napi_value(env: napi_sys::napi_env, value: napi_sys::napi_value) -> Result<Self> {
54    Ok(PromiseRaw::new(env, value))
55  }
56}
57
58impl<T> PromiseRaw<'_, T> {
59  pub(crate) fn new(env: sys::napi_env, inner: sys::napi_value) -> Self {
60    Self {
61      inner,
62      env,
63      _phantom: &PhantomData,
64    }
65  }
66}
67
68impl<'env, T: FromNapiValue> PromiseRaw<'env, T> {
69  /// Promise.then method
70  pub fn then<'then, Callback, U>(&self, cb: Callback) -> Result<PromiseRaw<'env, U>>
71  where
72    U: ToNapiValue,
73    Callback: 'then + FnOnce(CallbackContext<T>) -> Result<U>,
74  {
75    let mut then_fn = ptr::null_mut();
76    const THEN: &[u8; 5] = b"then\0";
77    check_status!(unsafe {
78      sys::napi_get_named_property(self.env, self.inner, THEN.as_ptr().cast(), &mut then_fn)
79    })?;
80    let mut then_callback = ptr::null_mut();
81    let executed = Box::into_raw(Box::new(false));
82    let rust_cb = Box::into_raw(Box::new((cb, executed)));
83    check_status!(
84      unsafe {
85        sys::napi_create_function(
86          self.env,
87          THEN.as_ptr().cast(),
88          4,
89          Some(raw_promise_then_callback::<T, U, Callback>),
90          rust_cb.cast(),
91          &mut then_callback,
92        )
93      },
94      "Create then function for PromiseRaw failed"
95    )?;
96    let mut new_promise = ptr::null_mut();
97    check_status!(
98      unsafe {
99        sys::napi_call_function(
100          self.env,
101          self.inner,
102          then_fn,
103          1,
104          [then_callback].as_ptr(),
105          &mut new_promise,
106        )
107      },
108      "Call the PromiseRaw::then failed"
109    )?;
110
111    // use `napi_wrap` to trigger the finalizer after the Promise is GCed
112    // Note: we don't use `napi_add_finalizer` here because it requires `napi5`
113    check_status!(
114      unsafe {
115        sys::napi_wrap(
116          self.env,
117          new_promise,
118          executed.cast(),
119          Some(promise_callback_finalizer::<T, U, Callback>),
120          rust_cb.cast(),
121          ptr::null_mut(),
122        )
123      },
124      "Wrap finalizer for PromiseRaw failed"
125    )?;
126
127    Ok(PromiseRaw::<U> {
128      env: self.env,
129      inner: new_promise,
130      _phantom: &PhantomData,
131    })
132  }
133
134  /// Promise.catch method
135  pub fn catch<'catch, E, U, Callback>(&self, cb: Callback) -> Result<PromiseRaw<'env, U>>
136  where
137    E: FromNapiValue,
138    U: ToNapiValue,
139    Callback: 'catch + FnOnce(CallbackContext<E>) -> Result<U>,
140  {
141    let mut catch_fn = ptr::null_mut();
142    const CATCH: &[u8; 6] = b"catch\0";
143    check_status!(unsafe {
144      sys::napi_get_named_property(self.env, self.inner, CATCH.as_ptr().cast(), &mut catch_fn)
145    })?;
146    let mut catch_callback = ptr::null_mut();
147    let executed = Box::into_raw(Box::new(false));
148    let rust_cb = Box::into_raw(Box::new((cb, executed)));
149    check_status!(
150      unsafe {
151        sys::napi_create_function(
152          self.env,
153          CATCH.as_ptr().cast(),
154          5,
155          Some(raw_promise_catch_callback::<E, U, Callback>),
156          rust_cb.cast(),
157          &mut catch_callback,
158        )
159      },
160      "Create catch function for PromiseRaw failed"
161    )?;
162    let mut new_promise = ptr::null_mut();
163    check_status!(
164      unsafe {
165        sys::napi_call_function(
166          self.env,
167          self.inner,
168          catch_fn,
169          1,
170          [catch_callback].as_mut_ptr().cast(),
171          &mut new_promise,
172        )
173      },
174      "Call the PromiseRaw::catch failed"
175    )?;
176
177    // use `napi_wrap` to trigger the finalizer after the Promise is GCed
178    // Note: we don't use `napi_add_finalizer` here because it requires `napi5`
179    check_status!(
180      unsafe {
181        sys::napi_wrap(
182          self.env,
183          new_promise,
184          executed.cast(),
185          Some(promise_callback_finalizer::<E, U, Callback>),
186          rust_cb.cast(),
187          ptr::null_mut(),
188        )
189      },
190      "Wrap finalizer for PromiseRaw failed"
191    )?;
192
193    Ok(PromiseRaw::<U> {
194      env: self.env,
195      inner: new_promise,
196      _phantom: &PhantomData,
197    })
198  }
199
200  /// Promise.finally method
201  pub fn finally<'finally, U, Callback>(&mut self, cb: Callback) -> Result<PromiseRaw<'env, T>>
202  where
203    U: ToNapiValue,
204    Callback: 'finally + FnOnce(Env) -> Result<U>,
205  {
206    let mut then_fn = ptr::null_mut();
207    const FINALLY: &[u8; 8] = b"finally\0";
208
209    check_status!(unsafe {
210      sys::napi_get_named_property(self.env, self.inner, FINALLY.as_ptr().cast(), &mut then_fn)
211    })?;
212    let mut then_callback = ptr::null_mut();
213    let rust_cb = Box::into_raw(Box::new(cb));
214    check_status!(
215      unsafe {
216        sys::napi_create_function(
217          self.env,
218          FINALLY.as_ptr().cast(),
219          7,
220          Some(raw_promise_finally_callback::<U, Callback>),
221          rust_cb.cast(),
222          &mut then_callback,
223        )
224      },
225      "Create then function for PromiseRaw failed"
226    )?;
227    let mut new_promise = ptr::null_mut();
228    check_status!(
229      unsafe {
230        sys::napi_call_function(
231          self.env,
232          self.inner,
233          then_fn,
234          1,
235          [then_callback].as_ptr(),
236          &mut new_promise,
237        )
238      },
239      "Call then callback on PromiseRaw failed"
240    )?;
241
242    Ok(Self {
243      env: self.env,
244      inner: new_promise,
245      _phantom: &PhantomData,
246    })
247  }
248
249  #[cfg(all(feature = "napi4", feature = "tokio_rt"))]
250  /// Convert `PromiseRaw<T>` to `Promise<T>`
251  ///
252  /// So you can await the Promise in Rust
253  pub fn into_sendable_promise(self) -> Result<Promise<T>> {
254    unsafe { Promise::from_napi_value(self.env, self.inner) }
255  }
256}
257
258pub(crate) fn validate_promise(
259  env: napi_sys::napi_env,
260  napi_val: napi_sys::napi_value,
261) -> Result<sys::napi_value> {
262  let mut is_promise = false;
263  check_status!(
264    unsafe { crate::sys::napi_is_promise(env, napi_val, &mut is_promise) },
265    "Failed to check if value is promise"
266  )?;
267  if !is_promise {
268    let mut deferred = ptr::null_mut();
269    let mut promise = ptr::null_mut();
270    check_status!(
271      unsafe { crate::sys::napi_create_promise(env, &mut deferred, &mut promise) },
272      "Failed to create promise"
273    )?;
274    const INVALID_ARG: &[u8; 11] = b"InvalidArg\0";
275    let mut err = ptr::null_mut();
276    let mut code = ptr::null_mut();
277    let mut message = ptr::null_mut();
278    check_status!(
279      unsafe {
280        crate::sys::napi_create_string_utf8(env, INVALID_ARG.as_ptr().cast(), 10, &mut code)
281      },
282      "Failed to create error message"
283    )?;
284    check_status!(
285      unsafe {
286        crate::sys::napi_create_string_utf8(
287          env,
288          c"Expected Promise object".as_ptr().cast(),
289          23,
290          &mut message,
291        )
292      },
293      "Failed to create error message"
294    )?;
295    check_status!(
296      unsafe { crate::sys::napi_create_error(env, code, message, &mut err) },
297      "Failed to create rejected error"
298    )?;
299    check_status!(
300      unsafe { crate::sys::napi_reject_deferred(env, deferred, err) },
301      "Failed to reject promise in validate"
302    )?;
303    return Ok(promise);
304  }
305  Ok(ptr::null_mut())
306}
307
308unsafe extern "C" fn raw_promise_then_callback<T, U, Cb>(
309  env: sys::napi_env,
310  cbinfo: sys::napi_callback_info,
311) -> sys::napi_value
312where
313  T: FromNapiValue,
314  U: ToNapiValue,
315  Cb: FnOnce(CallbackContext<T>) -> Result<U>,
316{
317  handle_then_callback::<T, U, Cb>(env, cbinfo)
318    .unwrap_or_else(|err| throw_error(env, err, "Error in Promise.then"))
319}
320
321#[inline]
322fn handle_then_callback<T, U, Cb>(
323  env: sys::napi_env,
324  cbinfo: sys::napi_callback_info,
325) -> Result<sys::napi_value>
326where
327  T: FromNapiValue,
328  U: ToNapiValue,
329  Cb: FnOnce(CallbackContext<T>) -> Result<U>,
330{
331  let mut callback_values = [ptr::null_mut()];
332  let mut rust_cb = ptr::null_mut();
333  check_status!(
334    unsafe {
335      sys::napi_get_cb_info(
336        env,
337        cbinfo,
338        &mut 1,
339        callback_values.as_mut_ptr(),
340        ptr::null_mut(),
341        &mut rust_cb,
342      )
343    },
344    "Get callback info from then callback failed"
345  )?;
346  let then_value: T = unsafe { FromNapiValue::from_napi_value(env, callback_values[0]) }?;
347  let cb: Box<(Cb, *mut bool)> = unsafe { Box::from_raw(rust_cb.cast()) };
348  let executed = unsafe { Box::leak(Box::from_raw(cb.1)) };
349  *executed = true;
350
351  unsafe {
352    U::to_napi_value(
353      env,
354      cb.0(CallbackContext {
355        env: Env(env),
356        value: then_value,
357      })?,
358    )
359  }
360}
361
362unsafe extern "C" fn raw_promise_catch_callback<E, U, Cb>(
363  env: sys::napi_env,
364  cbinfo: sys::napi_callback_info,
365) -> sys::napi_value
366where
367  E: FromNapiValue,
368  U: ToNapiValue,
369  Cb: FnOnce(CallbackContext<E>) -> Result<U>,
370{
371  handle_catch_callback::<E, U, Cb>(env, cbinfo)
372    .unwrap_or_else(|err| throw_error(env, err, "Error in Promise.catch"))
373}
374
375#[inline(always)]
376fn handle_catch_callback<E, U, Cb>(
377  env: sys::napi_env,
378  cbinfo: sys::napi_callback_info,
379) -> Result<sys::napi_value>
380where
381  E: FromNapiValue,
382  U: ToNapiValue,
383  Cb: FnOnce(CallbackContext<E>) -> Result<U>,
384{
385  let mut callback_values = [ptr::null_mut(); 1];
386  let mut rust_cb = ptr::null_mut();
387  check_status!(
388    unsafe {
389      sys::napi_get_cb_info(
390        env,
391        cbinfo,
392        &mut 1,
393        callback_values.as_mut_ptr(),
394        ptr::null_mut(),
395        &mut rust_cb,
396      )
397    },
398    "Get callback info from catch callback failed"
399  )?;
400  let catch_value: E = unsafe { FromNapiValue::from_napi_value(env, callback_values[0]) }?;
401  let cb: Box<(Cb, *mut bool)> = unsafe { Box::from_raw(rust_cb.cast()) };
402
403  let executed = unsafe { Box::leak(Box::from_raw(cb.1)) };
404  *executed = true;
405
406  unsafe {
407    U::to_napi_value(
408      env,
409      cb.0(CallbackContext {
410        env: Env(env),
411        value: catch_value,
412      })?,
413    )
414  }
415}
416
417unsafe extern "C" fn raw_promise_finally_callback<U, Cb>(
418  env: sys::napi_env,
419  cbinfo: sys::napi_callback_info,
420) -> sys::napi_value
421where
422  U: ToNapiValue,
423  Cb: FnOnce(Env) -> Result<U>,
424{
425  handle_finally_callback::<U, Cb>(env, cbinfo)
426    .unwrap_or_else(|err| throw_error(env, err, "Error in Promise.finally"))
427}
428
429#[inline(always)]
430fn handle_finally_callback<U, Cb>(
431  env: sys::napi_env,
432  cbinfo: sys::napi_callback_info,
433) -> Result<sys::napi_value>
434where
435  U: ToNapiValue,
436  Cb: FnOnce(Env) -> Result<U>,
437{
438  let mut rust_cb = ptr::null_mut();
439  check_status!(
440    unsafe {
441      sys::napi_get_cb_info(
442        env,
443        cbinfo,
444        &mut 0,
445        ptr::null_mut(),
446        ptr::null_mut(),
447        &mut rust_cb,
448      )
449    },
450    "Get callback info from finally callback failed"
451  )?;
452  let cb: Box<Cb> = unsafe { Box::from_raw(rust_cb.cast()) };
453
454  unsafe { U::to_napi_value(env, cb(Env(env))?) }
455}
456
457pub struct CallbackContext<T> {
458  pub env: Env,
459  pub value: T,
460}
461
462impl<T: ToNapiValue> ToNapiValue for CallbackContext<T> {
463  unsafe fn to_napi_value(env: napi_sys::napi_env, val: Self) -> Result<napi_sys::napi_value> {
464    T::to_napi_value(env, val.value)
465  }
466}
467
468#[inline(never)]
469fn throw_error(env: sys::napi_env, err: Error, default_msg: &str) -> sys::napi_value {
470  const GENERIC_FAILURE: &str = "GenericFailure\0";
471  let code = if err.status.as_ref().is_empty() {
472    GENERIC_FAILURE
473  } else {
474    err.status.as_ref()
475  };
476  let mut code_string = ptr::null_mut();
477  let msg = if err.reason.is_empty() {
478    default_msg
479  } else {
480    err.reason.as_ref()
481  };
482  let mut msg_string = ptr::null_mut();
483  let mut err = ptr::null_mut();
484  unsafe {
485    sys::napi_create_string_latin1(
486      env,
487      code.as_ptr().cast(),
488      code.len() as isize,
489      &mut code_string,
490    );
491    sys::napi_create_string_utf8(
492      env,
493      msg.as_ptr().cast(),
494      msg.len() as isize,
495      &mut msg_string,
496    );
497    sys::napi_create_error(env, code_string, msg_string, &mut err);
498    sys::napi_throw(env, err);
499  };
500  ptr::null_mut()
501}
502
503extern "C" fn promise_callback_finalizer<T, U, Cb>(
504  _env: sys::napi_env,
505  finalize_data: *mut c_void,
506  finalize_hint: *mut c_void,
507) where
508  T: FromNapiValue,
509  U: ToNapiValue,
510  Cb: FnOnce(CallbackContext<T>) -> Result<U>,
511{
512  if !unsafe { *Box::from_raw(finalize_data.cast()) } {
513    drop(unsafe { Box::from_raw(finalize_hint.cast::<Cb>()) });
514  }
515}