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}