Skip to main content

neon/types_impl/
promise.rs

1use std::ptr;
2
3use crate::{
4    context::{
5        internal::{ContextInternal, Env},
6        Context, Cx,
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    event::{Channel, JoinHandle, SendError},
18    types::extract::TryIntoJs,
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    #[cfg(feature = "napi-4")]
367    #[cfg_attr(docsrs, doc(cfg(feature = "napi-4")))]
368    /// Settle the [`JsPromise`] by sending a type that implements [`TryIntoJs`] to the main JavaScript thread.
369    ///
370    /// Usage is identical to [`Deferred::settle`].
371    ///
372    /// Returns a [`SendError`][crate::event::SendError] if sending the closure to the main JavaScript thread fails.
373    /// See [`Channel::try_send`][crate::event::Channel::try_send] for more details.
374    pub fn try_settle<V, T>(self, channel: &Channel, v: T) -> Result<JoinHandle<()>, SendError>
375    where
376        for<'cx> T: TryIntoJs<'cx, Value = V> + Send + 'static,
377        V: Value,
378    {
379        self.try_settle_with::<V, _>(channel, move |mut cx| v.try_into_js(&mut cx))
380    }
381
382    #[cfg(feature = "napi-4")]
383    #[cfg_attr(docsrs, doc(cfg(feature = "napi-4")))]
384    /// Settle the [`JsPromise`] by sending a type that implements [`TryIntoJs`] to the main JavaScript thread.
385    ///
386    /// Panics if there is a libuv error.
387    ///
388    /// ```
389    /// # use neon::prelude::*;
390    /// # fn example(mut cx: FunctionContext) -> JsResult<JsPromise> {
391    /// let channel = cx.channel();
392    /// let (deferred, promise) = cx.promise();
393    ///
394    /// deferred.settle(&channel, 42f64);
395    ///
396    /// # Ok(promise)
397    /// # }
398    /// ```
399    pub fn settle<V, T>(self, channel: &Channel, v: T) -> JoinHandle<()>
400    where
401        for<'cx> T: TryIntoJs<'cx, Value = V> + Send + 'static,
402        V: Value,
403    {
404        self.try_settle(channel, v).unwrap()
405    }
406
407    pub(crate) fn try_catch_settle<'a, C, V, F>(self, cx: C, f: F)
408    where
409        C: Context<'a>,
410        V: Value,
411        F: FnOnce(C) -> JsResult<'a, V>,
412    {
413        unsafe {
414            BOUNDARY.catch_failure(
415                cx.env().to_raw(),
416                Some(self.into_inner()),
417                move |_| match f(cx) {
418                    Ok(value) => value.to_local(),
419                    Err(_) => ptr::null_mut(),
420                },
421            );
422        }
423    }
424
425    pub(crate) fn into_inner(mut self) -> sys::Deferred {
426        self.internal.take().unwrap().0
427    }
428}
429
430#[repr(transparent)]
431pub(crate) struct NodeApiDeferred(sys::Deferred);
432
433unsafe impl Send for NodeApiDeferred {}
434
435#[cfg(feature = "napi-6")]
436impl NodeApiDeferred {
437    pub(crate) unsafe fn leaked(self, env: raw::Env) {
438        sys::promise::reject_err_message(
439            env,
440            self.0,
441            "`neon::types::Deferred` was dropped without being settled",
442        );
443    }
444}
445
446impl Drop for Deferred {
447    #[cfg(not(feature = "napi-6"))]
448    fn drop(&mut self) {
449        // If `None`, the `Deferred` has already been settled
450        if self.internal.is_none() {
451            return;
452        }
453
454        // Destructors are called during stack unwinding, prevent a double
455        // panic and instead prefer to leak.
456        if std::thread::panicking() {
457            eprintln!("Warning: neon::types::JsPromise leaked during a panic");
458            return;
459        }
460
461        // Only panic if the event loop is still running
462        if let Ok(true) = crate::context::internal::IS_RUNNING.try_with(|v| *v.borrow()) {
463            panic!("Must settle a `neon::types::JsPromise` with `neon::types::Deferred`");
464        }
465    }
466
467    #[cfg(feature = "napi-6")]
468    fn drop(&mut self) {
469        // If `None`, the `Deferred` has already been settled
470        if let Some(internal) = self.internal.take() {
471            let _ = self.drop_queue.call(DropData::Deferred(internal), None);
472        }
473    }
474}
475
476#[cfg(all(feature = "napi-5", feature = "futures"))]
477#[cfg_attr(docsrs, doc(cfg(all(feature = "napi-5", feature = "futures"))))]
478/// A type of JavaScript
479/// [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
480/// object that acts as a [`Future`](std::future::Future).
481///
482/// Unlike typical `Future` implementations, `JsFuture`s are eagerly executed
483/// because they are backed by a `Promise`.
484///
485/// # Example
486///
487/// This example uses a `JsFuture` to take asynchronous binary data and perform
488/// potentially expensive computations on that data in a Rust thread.
489///
490/// The example uses a [Tokio](https://tokio.rs) thread pool (allocated and
491/// stored on demand with a [`OnceCell`](https://crates.io/crates/once_cell))
492/// to run the computations.
493///
494/// ```
495/// # use neon::prelude::*;
496/// use neon::types::buffer::TypedArray;
497/// use once_cell::sync::OnceCell;
498/// use tokio::runtime::Runtime;
499///
500/// // Lazily allocate a Tokio runtime to use as the thread pool.
501/// fn runtime(cx: &mut Cx) -> NeonResult<&'static Runtime> {
502///     static RUNTIME: OnceCell<Runtime> = OnceCell::new();
503///
504///     RUNTIME
505///         .get_or_try_init(Runtime::new)
506///         .or_else(|err| cx.throw_error(&err.to_string()))
507/// }
508///
509/// // async_compute: Promise<Float64Array> -> Promise<number>
510/// //
511/// // Takes a promise that produces a typed array and returns a promise that:
512/// // - awaits the typed array from the original promise;
513/// // - computes a value from the contents of the array in a background thread; and
514/// // - resolves once the computation is completed
515/// pub fn async_compute(mut cx: FunctionContext) -> JsResult<JsPromise> {
516///     let nums: Handle<JsPromise> = cx.argument(0)?;
517///
518///     // Convert the JS Promise to a Rust Future for use in a compute thread.
519///     let nums = nums.to_future(&mut cx, |mut cx, result| {
520///         // Get the promise's result value (or throw if it was rejected).
521///         let value = result.or_throw(&mut cx)?;
522///
523///         // Downcast the result value to a Float64Array.
524///         let array: Handle<JsFloat64Array> = value.downcast_or_throw(&mut cx)?;
525///
526///         // Convert the typed array to a Rust vector.
527///         let vec = array.as_slice(&cx).to_vec();
528///         Ok(vec)
529///     })?;
530///
531///     // Construct a result promise which will be fulfilled when the computation completes.
532///     let (deferred, promise) = cx.promise();
533///     let channel = cx.channel();
534///     let runtime = runtime(&mut cx)?;
535///
536///     // Perform the computation in a background thread using the Tokio thread pool.
537///     runtime.spawn(async move {
538///         // Await the JsFuture, which yields Result<Vec<f64>, JoinError>.
539///         let result = match nums.await {
540///             // Perform the computation. In this example, we just calculate the sum
541///             // of all values in the array; more involved examples might be running
542///             // compression or decompression algorithms, encoding or decoding media
543///             // codecs, image filters or other media transformations, etc.
544///             Ok(nums) => Ok(nums.into_iter().sum::<f64>()),
545///             Err(err) => Err(err)
546///         };
547///     
548///         // Resolve the result promise with the result of the computation.
549///         deferred.settle_with(&channel, |mut cx| {
550///             let result = result.or_throw(&mut cx)?;
551///             Ok(cx.number(result))
552///         });
553///     });
554///
555///     Ok(promise)
556/// }
557/// ```
558pub struct JsFuture<T> {
559    // `Err` is always `Throw`, but `Throw` cannot be sent across threads
560    rx: oneshot::Receiver<Result<T, SendThrow>>,
561}
562
563#[cfg(all(feature = "napi-5", feature = "futures"))]
564#[cfg_attr(docsrs, doc(cfg(all(feature = "napi-5", feature = "futures"))))]
565impl<T> Future for JsFuture<T> {
566    type Output = Result<T, JoinError>;
567
568    fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context) -> Poll<Self::Output> {
569        match Pin::new(&mut self.rx).poll(cx) {
570            Poll::Ready(result) => {
571                // Flatten `Result<Result<T, SendThrow>, RecvError>` by mapping to
572                // `Result<T, JoinError>`. This can be simplified by replacing the
573                // closure with a try-block after stabilization.
574                // https://doc.rust-lang.org/beta/unstable-book/language-features/try-blocks.html
575                let get_result = move || Ok(result??);
576
577                Poll::Ready(get_result())
578            }
579            Poll::Pending => Poll::Pending,
580        }
581    }
582}