neon/event/mod.rs
1//! Exposes the JavaScript event loop for scheduling asynchronous events.
2//!
3//! ## The Event Loop
4//!
5//! The [_event loop_][event-loop] is how Node.js provides JavaScript programs
6//! access to concurrent events such as completion of [file][fs] or
7//! [network][net] operations, notification of scheduled [timers][timer], or
8//! receiving of messages from other [processes][process].
9//!
10//! When an asynchronous operation is started from JavaScript, it registers
11//! a JavaScript callback function to wait for the operation to complete. When
12//! the operation completes, the callback and the result data are added to an
13//! internal _event queue_ in the Node.js runtime so that the event can be
14//! processed in order.
15//!
16//! The event loop processes completed events one at a time in the JavaScript
17//! execution thread by calling the registered callback function with its result
18//! value as an argument.
19//!
20//! ## Creating Custom Events
21//!
22//! This module allows Neon programs to create new types of concurrent events
23//! in Rust and expose them to JavaScript as asynchronous functions.
24//!
25//! A common use for custom events is to run expensive or long-lived
26//! computations in a background thread without blocking the JavaScript
27//! thread. For example, using the [`psd` crate][psd-crate], a Neon program could
28//! asynchronously parse (potentially large) [PSD files][psd-file] in a
29//! background thread:
30//!
31//! ```
32//! # use neon::prelude::*;
33//! # #[cfg(not(feature = "napi-6"))]
34//! # type Channel = ();
35//! # fn parse(filename: String, callback: Root<JsFunction>, channel: Channel) { }
36//! # #[cfg(feature = "napi-6")]
37//! fn parse_async(mut cx: FunctionContext) -> JsResult<JsUndefined> {
38//! // The types `String`, `Root<JsFunction>`, and `Channel` can all be
39//! // sent across threads.
40//! let filename = cx.argument::<JsString>(0)?.value(&mut cx);
41//! let callback = cx.argument::<JsFunction>(1)?.root(&mut cx);
42//! let channel = cx.channel();
43//!
44//! // Spawn a background thread to complete the execution. The background
45//! // execution will _not_ block the JavaScript event loop.
46//! std::thread::spawn(move || {
47//! // Do the heavy lifting inside the background thread.
48//! parse(filename, callback, channel);
49//! });
50//!
51//! Ok(cx.undefined())
52//! }
53//! ```
54//!
55//! (Note that this usage of [`spawn`](std::thread::spawn) makes use of Rust's
56//! [`move`][move] syntax to transfer ownership of data to the background
57//! thread.)
58//!
59//! Upon completion of its task, the background thread can use the JavaScript
60//! callback and the channel to notify the main thread of the result:
61//!
62//! ```
63//! # use neon::prelude::*;
64//! # #[cfg(not(feature = "napi-6"))]
65//! # type Channel = ();
66//! # use psd::Psd;
67//! # use anyhow::{Context as _, Result};
68//! #
69//! fn psd_from_filename(filename: String) -> Result<Psd> {
70//! Psd::from_bytes(&std::fs::read(&filename)?).context("invalid psd file")
71//! }
72//!
73//! # #[cfg(feature = "napi-6")]
74//! fn parse(filename: String, callback: Root<JsFunction>, channel: Channel) {
75//! let result = psd_from_filename(filename);
76//!
77//! // Send a closure as a task to be executed by the JavaScript event
78//! // loop. This _will_ block the event loop while executing.
79//! channel.send(move |mut cx| {
80//! let callback = callback.into_inner(&mut cx);
81//!
82//! match result {
83//! Ok(psd) => {
84//! // Extract data from the parsed file.
85//! let obj = cx.empty_object()
86//! .prop(&mut cx, "width").set(psd.width())?
87//! .prop("height").set(psd.height())?
88//! .this();
89//!
90//! callback
91//! .bind(&mut cx)
92//! .args(((), obj))?
93//! .exec()?;
94//! }
95//! Err(err) => {
96//! use neon::types::extract::Error;
97//! callback
98//! .bind(&mut cx)
99//! .arg(Error::from(err))?
100//! .exec()?;
101//! }
102//! }
103//!
104//! Ok(())
105//! });
106//! }
107//! ```
108//!
109//! ## See also
110//!
111//! 1. Panu Pitkamaki. [Event loop from 10,000ft][event-loop].
112//!
113//! [event-loop]: https://bytearcher.com/articles/event-loop-10-000ft/
114//! [fs]: https://nodejs.org/dist/latest/docs/api/fs.html
115//! [net]: https://nodejs.org/dist/latest/docs/api/net.html
116//! [process]: https://nodejs.org/dist/latest/docs/api/process.html
117//! [timer]: https://nodejs.org/dist/latest/docs/api/timers.html
118//! [move]: https://doc.rust-lang.org/std/keyword.move.html
119//! [psd-crate]: https://crates.io/crates/psd
120//! [psd-file]: https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/
121
122#[cfg(feature = "napi-4")]
123mod channel;
124
125mod task;
126
127pub use self::task::TaskBuilder;
128
129#[cfg(all(feature = "napi-5", feature = "futures"))]
130pub(crate) use self::channel::SendThrow;
131#[cfg(feature = "napi-4")]
132pub use self::channel::{Channel, JoinError, JoinHandle, SendError};
133
134#[cfg(feature = "napi-4")]
135#[deprecated(since = "0.9.0", note = "Please use the Channel type instead")]
136#[doc(hidden)]
137pub type EventQueue = self::channel::Channel;
138
139#[cfg(feature = "napi-4")]
140#[deprecated(since = "0.9.0", note = "Please use the SendError type instead")]
141#[doc(hidden)]
142pub type EventQueueError = self::channel::SendError;