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