Module neon::event

source ·
Expand description

Exposes the JavaScript event loop for scheduling asynchronous events.

§The Event Loop

The event loop is how Node.js provides JavaScript programs access to concurrent events such as completion of file or network operations, notification of scheduled timers, or receiving of messages from other processes.

When an asynchronous operation is started from JavaScript, it registers a JavaScript callback function to wait for the operation to complete. When the operation completes, the callback and the result data are added to an internal event queue in the Node.js runtime so that the event can be processed in order.

The event loop processes completed events one at a time in the JavaScript execution thread by calling the registered callback function with its result value as an argument.

§Creating Custom Events

This module allows Neon programs to create new types of concurrent events in Rust and expose them to JavaScript as asynchronous functions.

A common use for custom events is to run expensive or long-lived computations in a background thread without blocking the JavaScript thread. For example, using the psd crate, a Neon program could asynchronously parse (potentially large) PSD files in a background thread:

fn parse_async(mut cx: FunctionContext) -> JsResult<JsUndefined> {
    // The types `String`, `Root<JsFunction>`, and `Channel` can all be
    // sent across threads.
    let filename = cx.argument::<JsString>(0)?.value(&mut cx);
    let callback = cx.argument::<JsFunction>(1)?.root(&mut cx);
    let channel = cx.channel();

    // Spawn a background thread to complete the execution. The background
    // execution will _not_ block the JavaScript event loop.
    std::thread::spawn(move || {
        // Do the heavy lifting inside the background thread.
        parse(filename, callback, channel);
    });

    Ok(cx.undefined())
}

(Note that this usage of spawn makes use of Rust’s move syntax to transfer ownership of data to the background thread.)

Upon completion of its task, the background thread can use the JavaScript callback and the channel to notify the main thread of the result:

fn psd_from_filename(filename: String) -> Result<Psd> {
    Psd::from_bytes(&std::fs::read(&filename)?).context("invalid psd file")
}

fn parse(filename: String, callback: Root<JsFunction>, channel: Channel) {
    let result = psd_from_filename(filename);

    // Send a closure as a task to be executed by the JavaScript event
    // loop. This _will_ block the event loop while executing.
    channel.send(move |mut cx| {
        let callback = callback.into_inner(&mut cx);
        let this = cx.undefined();
        let args = match result {
            Ok(psd) => {
                // Extract data from the parsed file.
                let width = cx.number(psd.width());
                let height = cx.number(psd.height());

                // Save the data in a result object.
                let obj = cx.empty_object();
                obj.set(&mut cx, "width", width)?;
                obj.set(&mut cx, "height", height)?;
                vec![
                    cx.null().upcast::<JsValue>(),
                    obj.upcast(),
                ]
            }
            Err(err) => {
                let err = cx.string(err.to_string());
                vec![
                    err.upcast::<JsValue>(),
                ]
            }
        };

        callback.call(&mut cx, this, args)?;

        Ok(())
    });
}

§See also

  1. Panu Pitkamaki. Event loop from 10,000ft.

Structs§

  • Channel for scheduling Rust closures to execute on the JavaScript main thread.
  • Error returned by JoinHandle::join indicating the associated closure panicked or threw an exception.
  • An owned permission to join on the result of a closure sent to the JavaScript main thread with Channel::send.
  • Error indicating that a closure was unable to be scheduled to execute on the event loop.
  • Node asynchronous task builder