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}