napi_h/bindgen_runtime/js_values/
promise.rs

1use std::ffi::CStr;
2use std::future;
3use std::pin::Pin;
4use std::ptr;
5use std::sync::{
6  atomic::{AtomicBool, Ordering},
7  Arc,
8};
9use std::task::{Context, Poll};
10
11use tokio::sync::oneshot::{channel, Receiver, Sender};
12
13use crate::{check_status, sys, Error, JsUnknown, NapiValue, Result, Status};
14
15use super::{FromNapiValue, TypeName, ValidateNapiValue};
16
17pub struct Promise<T: FromNapiValue> {
18  value: Pin<Box<Receiver<*mut Result<T>>>>,
19  aborted: Arc<AtomicBool>,
20}
21
22impl<T: FromNapiValue> Drop for Promise<T> {
23  fn drop(&mut self) {
24    self.aborted.store(true, Ordering::SeqCst);
25  }
26}
27
28impl<T: FromNapiValue> TypeName for Promise<T> {
29  fn type_name() -> &'static str {
30    "Promise"
31  }
32
33  fn value_type() -> crate::ValueType {
34    crate::ValueType::Object
35  }
36}
37
38impl<T: FromNapiValue> ValidateNapiValue for Promise<T> {
39  unsafe fn validate(
40    env: crate::sys::napi_env,
41    napi_val: crate::sys::napi_value,
42  ) -> Result<sys::napi_value> {
43    let mut is_promise = false;
44    check_status!(
45      unsafe { crate::sys::napi_is_promise(env, napi_val, &mut is_promise) },
46      "Failed to check if value is promise"
47    )?;
48    if !is_promise {
49      let mut deferred = ptr::null_mut();
50      let mut promise = ptr::null_mut();
51      check_status!(
52        unsafe { crate::sys::napi_create_promise(env, &mut deferred, &mut promise) },
53        "Failed to create promise"
54      )?;
55      let mut err = ptr::null_mut();
56      let mut code = ptr::null_mut();
57      let mut message = ptr::null_mut();
58      check_status!(
59        unsafe {
60          crate::sys::napi_create_string_utf8(
61            env,
62            CStr::from_bytes_with_nul_unchecked(b"InvalidArg\0").as_ptr(),
63            10,
64            &mut code,
65          )
66        },
67        "Failed to create error message"
68      )?;
69      check_status!(
70        unsafe {
71          crate::sys::napi_create_string_utf8(
72            env,
73            CStr::from_bytes_with_nul_unchecked(b"Expected Promise object\0").as_ptr(),
74            23,
75            &mut message,
76          )
77        },
78        "Failed to create error message"
79      )?;
80      check_status!(
81        unsafe { crate::sys::napi_create_error(env, code, message, &mut err) },
82        "Failed to create rejected error"
83      )?;
84      check_status!(
85        unsafe { crate::sys::napi_reject_deferred(env, deferred, err) },
86        "Failed to reject promise in validate"
87      )?;
88      return Ok(promise);
89    }
90    Ok(ptr::null_mut())
91  }
92}
93
94unsafe impl<T: FromNapiValue + Send> Send for Promise<T> {}
95
96impl<T: FromNapiValue> FromNapiValue for Promise<T> {
97  unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> crate::Result<Self> {
98    let mut then = ptr::null_mut();
99    let then_c_string = unsafe { CStr::from_bytes_with_nul_unchecked(b"then\0") };
100    check_status!(
101      unsafe { sys::napi_get_named_property(env, napi_val, then_c_string.as_ptr(), &mut then) },
102      "Failed to get then function"
103    )?;
104    let mut promise_after_then = ptr::null_mut();
105    let mut then_js_cb = ptr::null_mut();
106    let (tx, rx) = channel();
107    let aborted = Arc::new(AtomicBool::new(false));
108    let tx_ptr = Box::into_raw(Box::new((tx, aborted.clone())));
109    check_status!(
110      unsafe {
111        sys::napi_create_function(
112          env,
113          then_c_string.as_ptr(),
114          4,
115          Some(then_callback::<T>),
116          tx_ptr.cast(),
117          &mut then_js_cb,
118        )
119      },
120      "Failed to create then callback"
121    )?;
122    check_status!(
123      unsafe {
124        sys::napi_call_function(
125          env,
126          napi_val,
127          then,
128          1,
129          [then_js_cb].as_ptr(),
130          &mut promise_after_then,
131        )
132      },
133      "Failed to call then method"
134    )?;
135    let mut catch = ptr::null_mut();
136    let catch_c_string = unsafe { CStr::from_bytes_with_nul_unchecked(b"catch\0") };
137    check_status!(
138      unsafe {
139        sys::napi_get_named_property(env, promise_after_then, catch_c_string.as_ptr(), &mut catch)
140      },
141      "Failed to get then function"
142    )?;
143    let mut catch_js_cb = ptr::null_mut();
144    check_status!(
145      unsafe {
146        sys::napi_create_function(
147          env,
148          catch_c_string.as_ptr(),
149          5,
150          Some(catch_callback::<T>),
151          tx_ptr.cast(),
152          &mut catch_js_cb,
153        )
154      },
155      "Failed to create catch callback"
156    )?;
157    check_status!(
158      unsafe {
159        sys::napi_call_function(
160          env,
161          promise_after_then,
162          catch,
163          1,
164          [catch_js_cb].as_ptr(),
165          ptr::null_mut(),
166        )
167      },
168      "Failed to call catch method"
169    )?;
170    Ok(Promise {
171      value: Box::pin(rx),
172      aborted,
173    })
174  }
175}
176
177impl<T: FromNapiValue> future::Future for Promise<T> {
178  type Output = Result<T>;
179
180  fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
181    match self.value.as_mut().poll(cx) {
182      Poll::Pending => Poll::Pending,
183      Poll::Ready(v) => Poll::Ready(
184        v.map_err(|e| Error::new(Status::GenericFailure, format!("{}", e)))
185          .and_then(|v| unsafe { *Box::from_raw(v) }.map_err(Error::from)),
186      ),
187    }
188  }
189}
190
191unsafe extern "C" fn then_callback<T: FromNapiValue>(
192  env: sys::napi_env,
193  info: sys::napi_callback_info,
194) -> sys::napi_value {
195  let mut data = ptr::null_mut();
196  let mut resolved_value: [sys::napi_value; 1] = [ptr::null_mut()];
197  let mut this = ptr::null_mut();
198  let get_cb_status = unsafe {
199    sys::napi_get_cb_info(
200      env,
201      info,
202      &mut 1,
203      resolved_value.as_mut_ptr(),
204      &mut this,
205      &mut data,
206    )
207  };
208  debug_assert!(
209    get_cb_status == sys::Status::napi_ok,
210    "Get callback info from Promise::then failed"
211  );
212  let (sender, aborted) =
213    *unsafe { Box::from_raw(data as *mut (Sender<*mut Result<T>>, Arc<AtomicBool>)) };
214  if aborted.load(Ordering::SeqCst) {
215    return this;
216  }
217  let resolve_value_t = Box::new(unsafe { T::from_napi_value(env, resolved_value[0]) });
218  sender
219    .send(Box::into_raw(resolve_value_t))
220    .expect("Send Promise resolved value error");
221  this
222}
223
224unsafe extern "C" fn catch_callback<T: FromNapiValue>(
225  env: sys::napi_env,
226  info: sys::napi_callback_info,
227) -> sys::napi_value {
228  let mut data = ptr::null_mut();
229  let mut rejected_value: [sys::napi_value; 1] = [ptr::null_mut()];
230  let mut this = ptr::null_mut();
231  let mut argc = 1;
232  let get_cb_status = unsafe {
233    sys::napi_get_cb_info(
234      env,
235      info,
236      &mut argc,
237      rejected_value.as_mut_ptr(),
238      &mut this,
239      &mut data,
240    )
241  };
242  debug_assert!(
243    get_cb_status == sys::Status::napi_ok,
244    "Get callback info from Promise::catch failed"
245  );
246  let rejected_value = rejected_value[0];
247  let (sender, aborted) =
248    *unsafe { Box::from_raw(data as *mut (Sender<*mut Result<T>>, Arc<AtomicBool>)) };
249  if aborted.load(Ordering::SeqCst) {
250    return this;
251  }
252  sender
253    .send(Box::into_raw(Box::new(Err(Error::from(unsafe {
254      JsUnknown::from_raw_unchecked(env, rejected_value)
255    })))))
256    .expect("Send Promise resolved value error");
257  this
258}