Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question: how async function #81

Closed
ymwjbxxq opened this issue Dec 8, 2021 · 4 comments · Fixed by #82
Closed

Question: how async function #81

ymwjbxxq opened this issue Dec 8, 2021 · 4 comments · Fixed by #82

Comments

@ymwjbxxq
Copy link

ymwjbxxq commented Dec 8, 2021

Hello Team,

I am new in Rust, so bear with me.
I would like to build a native module for nodejs that make HTTP calls.
here is the code
https://github.com/ymwjbxxq/http_module_for_nodejs_with_rust

It looks like it is not really an asynchronous implementation with Tokio and the execution is super slow.

Is this the correct way to implement Neon?

Thanks,
Dan

@kjvalencik
Copy link
Member

@ymwjbxxq You are correct. There are two main issues:

  1. A new tokio runtime is being created for every call
  2. The JavaScript main thread is being blocked while the task completes

I am working on an example of using tokio and async. Here is what I have so far:

Note: This uses Neon 0.10.0-alpha.3

use neon::prelude::*;
use once_cell::sync::OnceCell;
use serde::Deserialize;
use tokio::runtime::Runtime;

#[derive(Deserialize)]
struct NodeRelease {
    version: String,
    date: String,
}

// Return a global tokio runtime or create one if it doesn't exist.
// Throws a JavaScript exception if the `Runtime` fails to create.
fn runtime<'a, C: Context<'a>>(cx: &mut C) -> NeonResult<&'static Runtime> {
    static RUNTIME: OnceCell<Runtime> = OnceCell::new();

    RUNTIME.get_or_try_init(|| Runtime::new().or_else(|err| cx.throw_error(err.to_string())))
}

// Get the verson of the currently running node process from `process.version`
fn node_version<'a, C: Context<'a>>(cx: &mut C) -> NeonResult<String> {
    let global = cx.global();

    let process = global
        .get(cx, "process")?
        .downcast_or_throw::<JsObject, _>(cx)?;

    let version = process
        .get(cx, "version")?
        .downcast_or_throw::<JsString, _>(cx)?
        .value(cx);

    Ok(version)
}

// Asynchronously fetch the list of Node releases. This will execute on the `tokio`
// thread pool.
async fn fetch_node_releases() -> Result<Vec<NodeRelease>, reqwest::Error> {
    reqwest::get("https://nodejs.org/dist/index.json")
        .await?
        .json()
        .await
}

// Asynchronously find a Node release from a version string
async fn fetch_node_release(version: &str) -> Result<Option<NodeRelease>, reqwest::Error> {
    let version = fetch_node_releases()
        .await?
        .into_iter()
        .find(|release| release.version == version);

    Ok(version)
}

// Get the release date of the currently running Node process.
// Returns a `Promise<string>` and executes asynchronously on the `tokio`
// thread pool.
fn node_release_date(mut cx: FunctionContext) -> JsResult<JsPromise> {
    let runtime = runtime(&mut cx)?;
    let version = node_version(&mut cx)?;
    let channel = cx.channel();

    // Create a JavaScript promise and a `deferred` handle for resolving it.
    // It is important to be careful not to perform failable actions after
    // creating the promise to avoid an unhandled rejection.
    let (deferred, promise) = cx.promise();

    // Spawn an `async` task on the tokio runtime. Only Rust types that are
    // `Send` may be moved into this block. `Context` may not be passed and all
    // JavaScript values must first be converted to Rust types.
    //
    // This task will _not_ block the JavaScript main thread.
    runtime.spawn(async move {
        // Inside this block, it is possible to `await` Rust `Future`
        let release = fetch_node_release(&version).await;

        // Settle the promise from the result of a closure. JavaScript exceptions
        // will be converted to a Promise rejection.
        //
        // This closure will execute on the JavaScript main thread. It should be
        // limited to converting Rust types to JavaScript values. Expensive operations
        // should be performed outside of it.
        deferred.settle_with(&channel, move |mut cx| {
            // Convert a `reqwest::Error` to a JavaScript exception
            let release = release.or_else(|err| cx.throw_error(err.to_string()))?;

            match release {
                // Resolve the promise with the release date
                Some(release) => Ok(cx.string(release.date)),

                // Reject the `Promise` if the version could not be found
                None => cx.throw_error(format!("Could not find version: {}", version)),
            }
        });
    });

    // Return the promise back to JavaScript
    Ok(promise)
}

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

    Ok(())
}

kjvalencik added a commit that referenced this issue Dec 8, 2021
Resolves #81
@kjvalencik kjvalencik mentioned this issue Dec 8, 2021
@kjvalencik
Copy link
Member

@ymwjbxxq Example can be found here: #82

@ymwjbxxq
Copy link
Author

ymwjbxxq commented Dec 8, 2021

Do you have an estimation for when the alpha becomes stable?
I think the example is excellent will simplify a lot.

@kjvalencik
Copy link
Member

It should be stable now. The alpha designation is hedging against breaking changes in the API.

kjvalencik added a commit that referenced this issue Dec 8, 2021
Resolves #81
kjvalencik added a commit that referenced this issue Jan 19, 2022
Resolves #81
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants