neon/types_impl/promise.rs
1use std::ptr;
2
3use crate::{
4 context::{
5 internal::{ContextInternal, Env},
6 Context,
7 },
8 handle::{internal::TransparentNoCopyWrapper, Handle},
9 object::Object,
10 result::JsResult,
11 sys::{self, no_panic::FailureBoundary, raw},
12 types::{private::ValueInternal, Value},
13};
14
15#[cfg(feature = "napi-4")]
16use crate::{
17 context::Cx,
18 event::{Channel, JoinHandle, SendError},
19};
20
21#[cfg(feature = "napi-6")]
22use crate::{
23 lifecycle::{DropData, InstanceData},
24 sys::tsfn::ThreadsafeFunction,
25};
26
27#[cfg(all(feature = "napi-5", feature = "futures"))]
28use {
29 crate::event::{JoinError, SendThrow},
30 crate::result::NeonResult,
31 crate::types::{JsFunction, JsValue},
32 std::future::Future,
33 std::pin::Pin,
34 std::sync::Mutex,
35 std::task::{self, Poll},
36 tokio::sync::oneshot,
37};
38
39#[cfg(any(feature = "napi-6", all(feature = "napi-5", feature = "futures")))]
40use std::sync::Arc;
41
42const BOUNDARY: FailureBoundary = FailureBoundary {
43 both: "A panic and exception occurred while resolving a `neon::types::Deferred`",
44 exception: "An exception occurred while resolving a `neon::types::Deferred`",
45 panic: "A panic occurred while resolving a `neon::types::Deferred`",
46};
47
48#[derive(Debug)]
49#[repr(transparent)]
50/// The type of JavaScript
51/// [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
52/// objects.
53///
54/// [`JsPromise`] instances may be constructed with [`Context::promise`], which
55/// produces both a promise and a [`Deferred`], which can be used to control
56/// the behavior of the promise. A `Deferred` struct is similar to the `resolve`
57/// and `reject` functions produced by JavaScript's standard
58/// [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise)
59/// constructor:
60///
61/// ```javascript
62/// let deferred;
63/// let promise = new Promise((resolve, reject) => {
64/// deferred = { resolve, reject };
65/// });
66/// ```
67///
68/// # Example
69///
70/// ```
71/// # use neon::prelude::*;
72/// fn resolve_promise(mut cx: FunctionContext) -> JsResult<JsPromise> {
73/// let (deferred, promise) = cx.promise();
74/// let msg = cx.string("Hello, World!");
75///
76/// deferred.resolve(&mut cx, msg);
77///
78/// Ok(promise)
79/// }
80/// ```
81///
82/// # Example: Asynchronous task
83///
84/// This example uses the [linkify](https://crates.io/crates/linkify) crate in an
85/// asynchronous task, i.e. a
86/// [Node worker pool](https://nodejs.org/en/docs/guides/dont-block-the-event-loop/)
87/// thread, to find all the links in a text string.
88///
89/// Alternate implementations might use a custom Rust thread or thread pool to avoid
90/// blocking the worker pool; for more information, see the [`JsFuture`] example.
91///
92/// ```
93/// # use neon::prelude::*;
94/// use linkify::{LinkFinder, LinkKind};
95/// # #[cfg(feature = "doc-dependencies")]
96/// use easy_cast::Cast; // for safe numerical conversions
97///
98/// # #[cfg(feature = "doc-dependencies")]
99/// fn linkify(mut cx: FunctionContext) -> JsResult<JsPromise> {
100/// let text = cx.argument::<JsString>(0)?.value(&mut cx);
101///
102/// let promise = cx
103/// .task(move || {
104/// let (indices, kinds): (Vec<_>, Vec<_>) = LinkFinder::new()
105/// // The spans() method fully partitions the text
106/// // into a sequence of contiguous spans, some of which
107/// // are plain text and some of which are links.
108/// .spans(&text)
109/// .map(|span| {
110/// // The first span starts at 0 and the rest start
111/// // at their preceding span's end index.
112/// let end: u32 = span.end().cast();
113///
114/// let kind: u8 = match span.kind() {
115/// Some(LinkKind::Url) => 1,
116/// Some(LinkKind::Email) => 2,
117/// _ => 0,
118/// };
119///
120/// (end, kind)
121/// })
122/// .unzip();
123/// (indices, kinds)
124/// })
125/// .promise(|mut cx, (indices, kinds)| {
126/// let indices = JsUint32Array::from_slice(&mut cx, &indices)?;
127/// let kinds = JsUint8Array::from_slice(&mut cx, &kinds)?;
128/// Ok(cx.empty_object()
129/// .prop(&mut cx, "indices")
130/// .set(indices)?
131/// .prop("kinds")
132/// .set(kinds)?
133/// .this())
134/// });
135///
136/// Ok(promise)
137/// }
138/// ```
139pub struct JsPromise(raw::Local);
140
141impl JsPromise {
142 pub(crate) fn new<'a, C: Context<'a>>(cx: &mut C) -> (Deferred, Handle<'a, Self>) {
143 let (deferred, promise) = unsafe { sys::promise::create(cx.env().to_raw()) };
144 let deferred = Deferred {
145 internal: Some(NodeApiDeferred(deferred)),
146 #[cfg(feature = "napi-6")]
147 drop_queue: InstanceData::drop_queue(cx),
148 };
149
150 (deferred, Handle::new_internal(JsPromise(promise)))
151 }
152
153 /// Creates a new `Promise` immediately resolved with the given value. If the value is a
154 /// `Promise` or a then-able, it will be flattened.
155 ///
156 /// `JsPromise::resolve` is useful to ensure a value that might not be a `Promise` or
157 /// might not be a native promise is converted to a `Promise` before use.
158 pub fn resolve<'a, C: Context<'a>, T: Value>(cx: &mut C, value: Handle<T>) -> Handle<'a, Self> {
159 let (deferred, promise) = cx.promise();
160 deferred.resolve(cx, value);
161 promise
162 }
163
164 /// Creates a nwe `Promise` immediately rejected with the given error.
165 pub fn reject<'a, C: Context<'a>, E: Value>(cx: &mut C, err: Handle<E>) -> Handle<'a, Self> {
166 let (deferred, promise) = cx.promise();
167 deferred.reject(cx, err);
168 promise
169 }
170
171 #[cfg(all(feature = "napi-5", feature = "futures"))]
172 #[cfg_attr(docsrs, doc(cfg(all(feature = "napi-5", feature = "futures"))))]
173 /// Creates a [`Future`](std::future::Future) that can be awaited to receive the result of a
174 /// JavaScript `Promise`.
175 ///
176 /// A callback must be provided that maps a `Result` representing the resolution or rejection of
177 /// the `Promise` and returns a value as the `Future` output.
178 ///
179 /// _Note_: Unlike `Future`, `Promise` are eagerly evaluated and so are `JsFuture`.
180 pub fn to_future<'a, O, C, F>(&self, cx: &mut C, f: F) -> NeonResult<JsFuture<O>>
181 where
182 O: Send + 'static,
183 C: Context<'a>,
184 F: FnOnce(Cx, Result<Handle<JsValue>, Handle<JsValue>>) -> NeonResult<O> + Send + 'static,
185 {
186 let then = self.get::<JsFunction, _, _>(cx, "then")?;
187
188 let (tx, rx) = oneshot::channel();
189 let take_state = {
190 // Note: If this becomes a bottleneck, `unsafe` could be used to avoid it.
191 // The promise spec guarantees that it will only be used once.
192 let state = Arc::new(Mutex::new(Some((f, tx))));
193
194 move || {
195 state
196 .lock()
197 .ok()
198 .and_then(|mut lock| lock.take())
199 // This should never happen because `self` is a native `Promise`
200 // and settling multiple times is a violation of the spec.
201 .expect("Attempted to settle JsFuture multiple times")
202 }
203 };
204
205 let resolve = JsFunction::new(cx, {
206 let take_state = take_state.clone();
207
208 move |mut cx| {
209 let (f, tx) = take_state();
210 let v = cx.argument::<JsValue>(0)?;
211
212 Cx::with_context(cx.env(), move |cx| {
213 // Error indicates that the `Future` has already dropped; ignore
214 let _ = tx.send(f(cx, Ok(v)).map_err(Into::into));
215 });
216
217 Ok(cx.undefined())
218 }
219 })?;
220
221 let reject = JsFunction::new(cx, {
222 move |mut cx| {
223 let (f, tx) = take_state();
224 let v = cx.argument::<JsValue>(0)?;
225
226 Cx::with_context(cx.env(), move |cx| {
227 // Error indicates that the `Future` has already dropped; ignore
228 let _ = tx.send(f(cx, Err(v)).map_err(Into::into));
229 });
230
231 Ok(cx.undefined())
232 }
233 })?;
234
235 then.exec(
236 cx,
237 Handle::new_internal(Self(self.0)),
238 [resolve.upcast(), reject.upcast()],
239 )?;
240
241 Ok(JsFuture { rx })
242 }
243}
244
245unsafe impl TransparentNoCopyWrapper for JsPromise {
246 type Inner = raw::Local;
247
248 fn into_inner(self) -> Self::Inner {
249 self.0
250 }
251}
252
253impl ValueInternal for JsPromise {
254 fn name() -> &'static str {
255 "Promise"
256 }
257
258 fn is_typeof<Other: Value>(cx: &mut Cx, other: &Other) -> bool {
259 unsafe { sys::tag::is_promise(cx.env().to_raw(), other.to_local()) }
260 }
261
262 fn to_local(&self) -> raw::Local {
263 self.0
264 }
265
266 unsafe fn from_local(_env: Env, h: raw::Local) -> Self {
267 Self(h)
268 }
269}
270
271impl Value for JsPromise {}
272
273impl Object for JsPromise {}
274
275/// A controller struct that can be used to resolve or reject a [`JsPromise`].
276///
277/// It is recommended to settle a [`Deferred`] with [`Deferred::settle_with`] to ensure
278/// exceptions are caught.
279///
280/// On Node-API versions less than 6, dropping a [`Deferred`] without settling will
281/// cause a panic. On Node-API 6+, the associated [`JsPromise`] will be automatically
282/// rejected.
283///
284/// # Examples
285///
286/// See [`JsPromise`], [`JsFuture`].
287pub struct Deferred {
288 internal: Option<NodeApiDeferred>,
289 #[cfg(feature = "napi-6")]
290 drop_queue: Arc<ThreadsafeFunction<DropData>>,
291}
292
293impl Deferred {
294 /// Resolve a [`JsPromise`] with a JavaScript value
295 pub fn resolve<'a, V, C>(self, cx: &mut C, value: Handle<V>)
296 where
297 V: Value,
298 C: Context<'a>,
299 {
300 unsafe {
301 sys::promise::resolve(cx.env().to_raw(), self.into_inner(), value.to_local());
302 }
303 }
304
305 /// Reject a [`JsPromise`] with a JavaScript value
306 pub fn reject<'a, V, C>(self, cx: &mut C, value: Handle<V>)
307 where
308 V: Value,
309 C: Context<'a>,
310 {
311 unsafe {
312 sys::promise::reject(cx.env().to_raw(), self.into_inner(), value.to_local());
313 }
314 }
315
316 #[cfg(feature = "napi-4")]
317 #[cfg_attr(docsrs, doc(cfg(feature = "napi-4")))]
318 /// Settle the [`JsPromise`] by sending a closure across a [`Channel`][`crate::event::Channel`]
319 /// to be executed on the main JavaScript thread.
320 ///
321 /// Usage is identical to [`Deferred::settle_with`].
322 ///
323 /// Returns a [`SendError`][crate::event::SendError] if sending the closure to the main JavaScript thread fails.
324 /// See [`Channel::try_send`][crate::event::Channel::try_send] for more details.
325 pub fn try_settle_with<V, F>(
326 self,
327 channel: &Channel,
328 complete: F,
329 ) -> Result<JoinHandle<()>, SendError>
330 where
331 V: Value,
332 F: FnOnce(Cx) -> JsResult<V> + Send + 'static,
333 {
334 channel.try_send(move |cx| {
335 self.try_catch_settle(cx, complete);
336 Ok(())
337 })
338 }
339
340 #[cfg(feature = "napi-4")]
341 #[cfg_attr(docsrs, doc(cfg(feature = "napi-4")))]
342 /// Settle the [`JsPromise`] by sending a closure across a [`Channel`][crate::event::Channel]
343 /// to be executed on the main JavaScript thread.
344 ///
345 /// Panics if there is a libuv error.
346 ///
347 /// ```
348 /// # use neon::prelude::*;
349 /// # fn example(mut cx: FunctionContext) -> JsResult<JsPromise> {
350 /// let channel = cx.channel();
351 /// let (deferred, promise) = cx.promise();
352 ///
353 /// deferred.settle_with(&channel, move |mut cx| Ok(cx.number(42)));
354 ///
355 /// # Ok(promise)
356 /// # }
357 /// ```
358 pub fn settle_with<V, F>(self, channel: &Channel, complete: F) -> JoinHandle<()>
359 where
360 V: Value,
361 F: FnOnce(Cx) -> JsResult<V> + Send + 'static,
362 {
363 self.try_settle_with(channel, complete).unwrap()
364 }
365
366 pub(crate) fn try_catch_settle<'a, C, V, F>(self, cx: C, f: F)
367 where
368 C: Context<'a>,
369 V: Value,
370 F: FnOnce(C) -> JsResult<'a, V>,
371 {
372 unsafe {
373 BOUNDARY.catch_failure(
374 cx.env().to_raw(),
375 Some(self.into_inner()),
376 move |_| match f(cx) {
377 Ok(value) => value.to_local(),
378 Err(_) => ptr::null_mut(),
379 },
380 );
381 }
382 }
383
384 pub(crate) fn into_inner(mut self) -> sys::Deferred {
385 self.internal.take().unwrap().0
386 }
387}
388
389#[repr(transparent)]
390pub(crate) struct NodeApiDeferred(sys::Deferred);
391
392unsafe impl Send for NodeApiDeferred {}
393
394#[cfg(feature = "napi-6")]
395impl NodeApiDeferred {
396 pub(crate) unsafe fn leaked(self, env: raw::Env) {
397 sys::promise::reject_err_message(
398 env,
399 self.0,
400 "`neon::types::Deferred` was dropped without being settled",
401 );
402 }
403}
404
405impl Drop for Deferred {
406 #[cfg(not(feature = "napi-6"))]
407 fn drop(&mut self) {
408 // If `None`, the `Deferred` has already been settled
409 if self.internal.is_none() {
410 return;
411 }
412
413 // Destructors are called during stack unwinding, prevent a double
414 // panic and instead prefer to leak.
415 if std::thread::panicking() {
416 eprintln!("Warning: neon::types::JsPromise leaked during a panic");
417 return;
418 }
419
420 // Only panic if the event loop is still running
421 if let Ok(true) = crate::context::internal::IS_RUNNING.try_with(|v| *v.borrow()) {
422 panic!("Must settle a `neon::types::JsPromise` with `neon::types::Deferred`");
423 }
424 }
425
426 #[cfg(feature = "napi-6")]
427 fn drop(&mut self) {
428 // If `None`, the `Deferred` has already been settled
429 if let Some(internal) = self.internal.take() {
430 let _ = self.drop_queue.call(DropData::Deferred(internal), None);
431 }
432 }
433}
434
435#[cfg(all(feature = "napi-5", feature = "futures"))]
436#[cfg_attr(docsrs, doc(cfg(all(feature = "napi-5", feature = "futures"))))]
437/// A type of JavaScript
438/// [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
439/// object that acts as a [`Future`](std::future::Future).
440///
441/// Unlike typical `Future` implementations, `JsFuture`s are eagerly executed
442/// because they are backed by a `Promise`.
443///
444/// # Example
445///
446/// This example uses a `JsFuture` to take asynchronous binary data and perform
447/// potentially expensive computations on that data in a Rust thread.
448///
449/// The example uses a [Tokio](https://tokio.rs) thread pool (allocated and
450/// stored on demand with a [`OnceCell`](https://crates.io/crates/once_cell))
451/// to run the computations.
452///
453/// ```
454/// # use neon::prelude::*;
455/// use neon::types::buffer::TypedArray;
456/// use once_cell::sync::OnceCell;
457/// use tokio::runtime::Runtime;
458///
459/// // Lazily allocate a Tokio runtime to use as the thread pool.
460/// fn runtime(cx: &mut Cx) -> NeonResult<&'static Runtime> {
461/// static RUNTIME: OnceCell<Runtime> = OnceCell::new();
462///
463/// RUNTIME
464/// .get_or_try_init(Runtime::new)
465/// .or_else(|err| cx.throw_error(&err.to_string()))
466/// }
467///
468/// // async_compute: Promise<Float64Array> -> Promise<number>
469/// //
470/// // Takes a promise that produces a typed array and returns a promise that:
471/// // - awaits the typed array from the original promise;
472/// // - computes a value from the contents of the array in a background thread; and
473/// // - resolves once the computation is completed
474/// pub fn async_compute(mut cx: FunctionContext) -> JsResult<JsPromise> {
475/// let nums: Handle<JsPromise> = cx.argument(0)?;
476///
477/// // Convert the JS Promise to a Rust Future for use in a compute thread.
478/// let nums = nums.to_future(&mut cx, |mut cx, result| {
479/// // Get the promise's result value (or throw if it was rejected).
480/// let value = result.or_throw(&mut cx)?;
481///
482/// // Downcast the result value to a Float64Array.
483/// let array: Handle<JsFloat64Array> = value.downcast_or_throw(&mut cx)?;
484///
485/// // Convert the typed array to a Rust vector.
486/// let vec = array.as_slice(&cx).to_vec();
487/// Ok(vec)
488/// })?;
489///
490/// // Construct a result promise which will be fulfilled when the computation completes.
491/// let (deferred, promise) = cx.promise();
492/// let channel = cx.channel();
493/// let runtime = runtime(&mut cx)?;
494///
495/// // Perform the computation in a background thread using the Tokio thread pool.
496/// runtime.spawn(async move {
497/// // Await the JsFuture, which yields Result<Vec<f64>, JoinError>.
498/// let result = match nums.await {
499/// // Perform the computation. In this example, we just calculate the sum
500/// // of all values in the array; more involved examples might be running
501/// // compression or decompression algorithms, encoding or decoding media
502/// // codecs, image filters or other media transformations, etc.
503/// Ok(nums) => Ok(nums.into_iter().sum::<f64>()),
504/// Err(err) => Err(err)
505/// };
506///
507/// // Resolve the result promise with the result of the computation.
508/// deferred.settle_with(&channel, |mut cx| {
509/// let result = result.or_throw(&mut cx)?;
510/// Ok(cx.number(result))
511/// });
512/// });
513///
514/// Ok(promise)
515/// }
516/// ```
517pub struct JsFuture<T> {
518 // `Err` is always `Throw`, but `Throw` cannot be sent across threads
519 rx: oneshot::Receiver<Result<T, SendThrow>>,
520}
521
522#[cfg(all(feature = "napi-5", feature = "futures"))]
523#[cfg_attr(docsrs, doc(cfg(all(feature = "napi-5", feature = "futures"))))]
524impl<T> Future for JsFuture<T> {
525 type Output = Result<T, JoinError>;
526
527 fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context) -> Poll<Self::Output> {
528 match Pin::new(&mut self.rx).poll(cx) {
529 Poll::Ready(result) => {
530 // Flatten `Result<Result<T, SendThrow>, RecvError>` by mapping to
531 // `Result<T, JoinError>`. This can be simplified by replacing the
532 // closure with a try-block after stabilization.
533 // https://doc.rust-lang.org/beta/unstable-book/language-features/try-blocks.html
534 let get_result = move || Ok(result??);
535
536 Poll::Ready(get_result())
537 }
538 Poll::Pending => Poll::Pending,
539 }
540 }
541}