neon-bindings / neon

Rust bindings for writing safe and fast native Node.js modules.
https://www.neon-bindings.com/
Apache License 2.0
7.98k stars 282 forks source link

How to handle a non-block callback that provides a value back to Rust? #1035

Closed alshdavid closed 4 months ago

alshdavid commented 4 months ago

I'm trying to call a non-blocking function in Javascript provided by Rust where the value produced by JavaScript is made available to Rust.

const napi = require('./index.node')

napi.add((a, b, resolve) => {
  resolve(a + b)
})

I am trying something like this:

use std::{sync::mpsc::channel, thread};

use neon::prelude::*;

#[neon::main]
fn main(mut cx: ModuleContext) -> NeonResult<()> {
  cx.export_function("add", add)?;
  Ok(())
}

fn add(mut cx: FunctionContext) -> JsResult<JsNumber> {
  let arg0: Handle<JsFunction> = cx.argument(0)?;
  let (tx, rx) = channel::<f64>();

  let a = cx.number(1);
  let b = cx.number(2);

  let resolve = cx.function(move |cx: FunctionContext| -> JsResult<JsUndefined> {
    let arg0: Handle<JsNumber> = cx.argument(0)?;
    let result = arg0.value(&mut cx);
    tx.send(result).unwrap();
    Ok(cx.undefined())
  });

  let result: Handle<JsNumber> = arg0
    .call_with(&mut cx)
    .arg(a)
    .arg(b)
    .arg(resolve)
    .apply(&mut cx).unwrap();

  thread::spawn(move || {
    let result = rx.recv().unwrap();
    println!("got {}", result);
  });

  return Ok(cx.number(42 as f64));
}

However cx.function is not a thing - wondering what the correct approach for this would be?

alshdavid commented 4 months ago

Figured it out:

use std::{sync::mpsc::channel, thread};

use neon::prelude::*;

#[neon::main]
fn main(mut cx: ModuleContext) -> NeonResult<()> {
  cx.export_function("add", add)?;
  Ok(())
}

fn add(mut cx: FunctionContext) -> JsResult<JsNumber> {
  let arg0: Handle<JsFunction> = cx.argument(0)?;
  let (tx, rx) = channel::<f64>();

  let a = cx.number(1);
  let b = cx.number(2);

  let resolve = JsFunction::new(&mut cx, move |mut cx: FunctionContext| -> JsResult<JsUndefined> {
    let arg0: Handle<JsNumber> = cx.argument(0)?;
    let result = arg0.value(&mut cx);
    tx.send(result).unwrap();
    Ok(cx.undefined())
  })?;

  arg0
    .call_with(&mut cx)
    .arg(a)
    .arg(b)
    .arg(resolve)
    .exec(&mut cx)?;

  thread::spawn(move || {
    let result = rx.recv().unwrap();
    println!("got {}", result);
  });

  return Ok(cx.number(42 as f64));
}
const napi = require('./index.node')

napi.add(async (a, b, resolve) => {
  console.log('starting')
  await new Promise(res => setTimeout(res, 1000))
  resolve(a + b)
})

napi.add(async (a, b, resolve) => {
  console.log('starting')
  await new Promise(res => setTimeout(res, 1000))
  resolve(a + b)
})

Outputs:

'starting'
'starting'
...
"got 3"
"got 3"
kjvalencik commented 4 months ago

Glad you figured out something that worked! If you are using an async runtime, the futures feature will also help.

https://github.com/neon-bindings/neon/blob/2e0a0f1bd5b2b1b2aaed465437ba6bcec2d5db59/test/napi/src/js/futures.rs#L49-L82

alshdavid commented 4 months ago

Looks like I was a bit quick to close the ticket, my example was sort of working but incomplete. Will look at the futures feature to see if that works. Thanks!