node.js是一个非常流行的JavaScript运行时,用于编写后端应用程序。 它的灵活性和非阻塞性使其成为编写API首要选择。

由于它是脚本语言,JavaScript可能比较慢。 但由于V8的优化,它足以实现实际应用。 也就是说,Node.js对计算密集任务不利; 由于它是单线程的,因此阻塞主线程进行长期计算是不值得的。 这就是worker线程使用的地方。Node.js对worker线程有支持,因此它可用于执行密集型计算。

只要worker线程达到一定程度,JavaScript仍然很慢。 此外,在所有支持的LTS版本中没有worker线程。 幸运的是,我们可以使用Rust构建Node.js的原声模块。 FFI是另一种替代方案,但它比原生方法慢。 Rust更快,并具有无限制的并发。 由于Rust有一个非常小的运行时(其实这不应该称为运行时),生成的二进制大小也非常小。

什么是Rust?

RURT是Mozilla的系统编程语言。 它默认可以调用C库,并将函数导出到C。

可以通过各种方法在Node.js上下文中调用Rust。 下面列出了最广泛使用的一些示例。

  • 您可以使用FFI,在Node.js和Rust中,但这非常慢
  • 您可以使用webassembly来创建node_module,但所有Node.js函数都不可用
  • 您可以使用原生模块

什么是native addon?

Node.js addons是用动态链接的C ++编写的共享对象。 您可以使用require()函数将它们加载到Node.js中,并使用它们,就像它们是普通node.js模块一样。 它们主要在Node.js和C/C ++库中的JavaScript运行时之间提供接口。

原生addon提供了一个简单的接口,通过在V8运行时加载它来与另一个二进制一起使用。 在跨语言调用是非常快速和安全的。 目前,Node.js支持两种类型的addons方法:C ++ Addons和N-API C ++/C插件。

C++ addons

C ++ addons是可以由Node.js安装并在运行时使用的对象。 由于C ++是编译语言,因此这些插件非常快。 C ++具有各种生产库,可用于Node.js生态系统。 许多流行的库使用原生插件来提高性能和代码质量。

N-API C++/C addons

C ++ Addons的主要问题是每个更改时您需要将它们重新编译到底层JavaScript运行时。 它导致维护迟缓的问题。 N-API尝试通过引入标准应用二进制接口(ABI)来消除此问题。 C头文件仍然向后兼容。 这意味着您可以使用特定版本的node.js编译的Addons,任何版本大于它都会被编译。 您将使用此方法实现您的插件。

从哪里开始Rust?

Rust可以模拟C库的行为。 换句话说,它以导出C可以理解的使用格式。 rust调用C函数来访问和使用Node.js提供的API。 这些API提供了用于创建JavaScript string,array,number,error,object,function等。 但我们需要讲述一些rust的外部功能,结构,指针等。

#[repr(C)]
struct MyRustStruct {
    a: i32,
}
extern "C" fn rust_world_callback(target: *mut RustObject, a: i32) {
    println!("Function is called from C world", a);
    unsafe {
        // Do something on rust struct
        (*target).a = a;
    }
}
extern {
   fn register_callback(target: *mut MyRustStruct,
                        cb: extern fn(*mut MyRustStruct, i32)) -> i32;
   fn trigger_callback();
}

Rust以不同方式在内存中存放结构,因此我们需要告诉它使用C风格。 通过手动创建这些函数是一种痛苦,因此我们将使用称为nodejs-sys的箱子,它为使用N-API创建一个很好的bindgen定义。

bindgen自动生成RUST FFI绑定到C和C ++库。

GIF of the Joker Saying "Don't Say I Didn't Warn You"

设置您的项目

对于本教程,您必须在系统上具有node.jsRust ,包括CARGONPM 。 建议使用Rustup来安装Rust和nvm安装node.js.

创建名为rust-addon的目录,并通过运行npm init初始化新的NPM项目。 接下来,使用cargo init --lib 初始化一个cargo项目。 您的项目目录应该如下所示:

├── Cargo.toml
├── package.json
└── src
    └── lib.rs

配置Rust并编译为addon

我们需要Rust编译为动态C库或对象。 配置Cargo以编译到Linux上的.so文件,在OSX上的.dylib,在Windows上的.dll。 rust可以使用Rustc标识生成许多不同类型的动态库。

[package]
name = "rust-addon"
version = "0.1.0"
authors = ["Anshul Goyal <[email protected]>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[lib]
crate-type=["cdylib"]

[dependencies]
nodejs-sys = "0.2.0"

lib键提供配置RUSTC的选项。 name键以lib{name}的形式给出了库名称,而type提供了库的类型,它应该被编译为 cdylibrlibcdylib创建一个动态链接的C库。 此共享对象的行为类似于C库。

n-api入门

创建我们的N-API库。 我们需要添加依赖项。 nodejs-sys提供napi-header文件所需的绑定。 napi_register_module_v1是插件的入口点。 N-API文档建议模块注册的N-API_MODULE_INIT宏,它编译为napi_register_module_v1函数。

Node.js调用此函数并使用名为napi_env的不透明指针提供它,这是指在JavaScript运行时中的模块的配置。 后者是代表JavaScript值的另一个不透明指针,其实际上是称为导出的对象。 这些导出与JavaScript中的Node.js模块提供的那些导出相同。

use nodejs_sys::{napi_create_string_utf8, napi_env, napi_set_named_property, napi_value};
use std::ffi::CString;
#[no_mangle]
pub unsafe extern "C" fn napi_register_module_v1(
    env: napi_env,
    exports: napi_value,
) -> nodejs_sys::napi_value {
// creating a C string
    let key = CString::new("hello").expect("CString::new failed");
// creating a memory location where the pointer to napi_value will be saved
    let mut local: napi_value = std::mem::zeroed();
// creating a C string
    let value = CString::new("world!").expect("CString::new failed");
// creating napi_value for the string
    napi_create_string_utf8(env, value.as_ptr(), 6, &mut local);
// setting the string on the exports object
    napi_set_named_property(env, exports, key.as_ptr(), local);
// returning the object 
    exports
}

Rust有自己String类型和借用的C字符串的表示形式。 两者都始终在UTF-8编码中,并且可以在中间包含空字节。 如果您查看构成字符串的字节,其中可能存在\0。 两个Stringstr明确地存储它们的长度; 字符串结束时像C字符串没有空终止符号。

rust字符串与C中的生锈字符串非常不同,因此我们需要在使用N-API函数之前将我们的rust字符串更改为C字符串。 由于exports是由exports表示的对象,我们可以将函数,字符串,array或任何其他JavaScript对象添加为键值对。

要将键添加到JavaScript对象,您可以使用N-API提供的方法napi_set_named_property。 此函数帮助我们添加属性到对象; 指向一个字符串的指针将被用作我们属性的密钥;指针指向javascript值,可以是字符串,array等; napi_env,它在rust和node.js之间起到锚作用。

您可以使用N-API函数来创建任何JavaScript值。 例如,我们在此处使用napi_create_string_utf8来创建一个字符串。 我们在环境中传递指向字符串的指针,字符串的长度,以及一个指向空存储位置的指针,可以将指针写入新创建的值。 所有这些代码都不安全,因为它包含许多对外部函数的调用,编译器无法提供保证。

重要的是要理解nodejs-sys 它为您使用的函数提供所需的定义,而不是其实现。 N-API实现包含在Node.js中,您可以从rust代码中调用它。

在Node.js中使用原生模块addon

下一步是为不同的操作系统添加链接配置,然后编译它。

创建一个build.rs文件并添加一些配置标识,用于将N-API文件链接在不同的操作系统上。

fn main() {
    println!("cargo:rustc-cdylib-link-arg=-undefined");
    if cfg!(target_os = "macos") {
        println!("cargo:rustc-cdylib-link-arg=dynamic_lookup");
    }
}

您的目录应该如下所示:

├── build.rs
├── Cargo.lock
├── Cargo.toml
├── index.node
├── package.json
├── src
    └── lib.rs

现在您需要编译rust addon。 您可以使用简单的命令cargo build --release。 这将花一些时间在第一次运行的时候。

编译模块后,将此二进制文件的副本从./target/release/libnative.so创建到根目录,并将其重命名为index.node。 由cargo创建的二进制文件可能具有不同的扩展或名称,具体取决于您的crate设置和操作系统。

现在您可以在Node.js中require并使用它。 您也可以在脚本中使用它。 例如:

let addon=require('./index.node');
console.log(addon.hello);
Using the Addon in Node

接下来,我们将继续创建函数,数组和promises,并使用libuv thread-pool执行繁重的任务而不阻止主线程。

深入理解N-API

现在您知道如何使用N-API和RUST实现常见模式。 一个非常常见的模式是导出函数,可以作为库或node.js模块的供用户调用。 让我们开始创建函数。

您应该使用napi_create_function创建函数,以便您可以从Node.js使用它们。 您可以将这些函数添加为Node.js属性导出。

创建函数

JavaScript函数也由napi_value指针表示。 n-api函数非常易于创建和使用。

use nodejs_sys::{
    napi_callback_info, napi_create_function, napi_create_string_utf8, napi_env,
    napi_set_named_property, napi_value,
};
use std::ffi::CString;
pub unsafe extern "C" fn say_hello(env: napi_env, _info: napi_callback_info) -> napi_value {
// creating  a javastring string
    let mut local: napi_value = std::mem::zeroed();
    let p = CString::new("Hello from rust").expect("CString::new    failed");
    napi_create_string_utf8(env, p.as_ptr(), 13, &mut local);
// returning the javascript string
    local
}
#[no_mangle]
pub unsafe extern "C" fn napi_register_module_v1(
    env: napi_env,
    exports: napi_value,
) -> nodejs_sys::napi_value {
// creating a C String
    let p = CString::new("myFunc").expect("CString::new failed");
// creating a location where pointer to napi_value be written
    let mut local: napi_value = std::mem::zeroed();
    napi_create_function(
        env,
// pointer to function name
        p.as_ptr(),
// length of function name
        5,
// rust function
        Some(say_hello),
// context which can be accessed by the rust function
        std::ptr::null_mut(),
// output napi_value
        &mut local,
    );
// set function as property 
    napi_set_named_property(env, exports, p.as_ptr(), local);
// returning exports
    exports
}
Function Created With N-API

在上面的示例中, 我们使用napi_create_function创建了一个函数,函数命名为say_hello,它有以下参数:

  • napi_env
  • 函数名称的字符串,用于JavaScript函数
  • 函数名称长度
  • 当JavaScript调用新创建的函数时执行的函数
  • 用户可以传递上下文数据并从RUST访问上下文
  • 可以保存指向JavaScript函数的指针
  • 当您创建此函数时,将其添加为exports对象的属性,以便您可以将其从JavaScript中使用

Rust的函数必须具有相同的签名,如示例所示。 我们将讨论下一步如何访问函数内的参数使用napi_callback_info。 我们也可以从函数和其他参数访问它。

访问参数

函数参数非常重要。 N-API提供访问这些参数的方法。 napi_callback_info提供指向javascript侧的函数的详细信息的指针。

use nodejs_sys::{
    napi_callback_info, napi_create_double, napi_create_function, napi_env, napi_get_cb_info,
    napi_get_value_double, napi_set_named_property, napi_value,
};
use std::ffi::CString;

pub unsafe extern "C" fn add(env: napi_env, info: napi_callback_info) -> napi_value {
// creating a buffer where napi_value of argument be written
    let mut buffer: [napi_value; 2] = std::mem::MaybeUninit::zeroed().assume_init();
// max number of arguments
    let mut argc = 2 as usize;
// getting arguments and value of this
    napi_get_cb_info(
        env,
        info,
        &mut argc,
        buffer.as_mut_ptr(),
        std::ptr::null_mut(),
        std::ptr::null_mut(),
    );
// converting napi to f64
    let mut x = 0 as f64;
    let mut y = 0 as f64;
    napi_get_value_double(env, buffer[0], &mut x);
    napi_get_value_double(env, buffer[1], &mut y);
// creating the return value
    let mut local: napi_value = std::mem::zeroed();
    napi_create_double(env, x + y, &mut local);
// returning the result
    local
}

#[no_mangle]
pub unsafe extern "C" fn napi_register_module_v1(
    env: napi_env,
    exports: napi_value,
) -> nodejs_sys::napi_value {
// creating a function name
    let p = CString::new("myFunc").expect("CString::new failed");
    let mut local: napi_value = std::mem::zeroed();
// creating the function
    napi_create_function(
        env,
        p.as_ptr(),
        5,
        Some(add),
        std::ptr::null_mut(),
        &mut local,
    );
// setting function as property
    napi_set_named_property(env, exports, p.as_ptr(), local);
// returning exports
    exports
}
Accessing Arguments With N-API

使用napi_get_cb_info获取参数。 必须提的参数:

  • napi_env
  • 指针信息
  • 预期参数的数量
  • 一个缓冲区,其中参数可以写为napi_value
  • 创建JavaScript函数时将元数据存储在提供的内存中
  • 可以写入该值指针的存储位置

我们需要创建一个具有内存位置的数组,其中C可以向参数写一个指针,我们可以将此指针缓冲区传递给N-API函数。 我们也得到了this,但在这个例子中我们没有使用它。

使用字符串作为参数

大多数情况下,您需要使用JavaScript中的字符串。 创建和获取字符串的值非常简单。 使用napi_get_value_string_utf8并调用此函数两次:第一次获取长度和第二次以获取字符串的值。

use nodejs_sys::{
    napi_callback_info, napi_create_function, napi_env, napi_get_cb_info, napi_get_undefined,
    napi_get_value_string_utf8, napi_set_named_property, napi_value,
};

use std::ffi::CString;

pub unsafe extern "C" fn print(env: napi_env, info: napi_callback_info) -> napi_value {
// creating a buffer of arguments
    let mut buffer: [napi_value; 1] = std::mem::MaybeUninit::zeroed().assume_init();
    let mut argc = 1 as usize;
// getting arguments
    napi_get_cb_info(
        env,
        info,
        &mut argc,
        buffer.as_mut_ptr(),
        std::ptr::null_mut(),
        std::ptr::null_mut(),
    );
    let mut len = 0;
// getting length by passing null buffer
    napi_get_value_string_utf8(env, buffer[0], std::ptr::null_mut(), 0, &mut len);
    let size = len as usize;
// creating a buffer where string can be placed
    let mut ve: Vec<u8> = Vec::with_capacity(size + 1);
    let raw = ve.as_mut_ptr();
// telling rust not manage the vector
    std::mem::forget(ve);
    let mut cap = 0;
// getting the string value from napi_value
    let _s = napi_get_value_string_utf8(env, buffer[0], raw as *mut i8, size + 1, &mut cap);
    let s = String::from_raw_parts(raw, cap as usize, size);
// printing the string
    println!("{}", s);
// creating an undefined
    let mut und: napi_value = std::mem::zeroed();
    napi_get_undefined(env, &mut und);
// returning undefined
    und
}

#[no_mangle]
pub unsafe extern "C" fn napi_register_module_v1(
    env: napi_env,
    exports: napi_value,
) -> nodejs_sys::napi_value {
    let p = CString::new("myFunc").expect("CString::new failed");
    let mut local: napi_value = std::mem::zeroed();
    napi_create_function(
        env,
        p.as_ptr(),
        5,
        Some(print),
        std::ptr::null_mut(),
        &mut local,
    );
    napi_set_named_property(env, exports, p.as_ptr(), local);
    exports
}
String Arguments in N-API

您需要将几个参数传递给napi_create_string_utf8以创建字符串。 如果将空指针传递为缓冲区,则需要指定字符串的长度。 需要以下参数:

  • napi_env
  • napi_value指向javascript side中的字符串
  • 如果null给出字符串的长度,则要写字符串的缓冲区
  • buffer的长度
  • 写入缓冲区的字节

使用promises和Libuv线程池

阻塞Node.js的主线程以进行计算,这不是一个好主意。 您可以使用Libuv线程进行繁重任务。

首先,创造一个promise。 promise将根据您工作的成功与否来决定reject或resolve。 为此,您需要创建三个函数。 第一个从JavaScript调用,并且控件将传递给第二个函数,它在Libuv线程上运行,无法访问JavaScript。 第三个函数在第二个之后调用具有访问JavaScript侧能力。 您可以使用libuv线程的napi_create_async_work方法。

创建一个promise

创建一个承诺,只需使用napi_create_promise。 这将提供一个指针napi_deferred,然后使用以下函数resolve或reject承诺:

  • napi_resolve_deferred
  • napi_reject_deferred
  • napi_resolve_deferred
  • napi_reject_deferred

错误处理

您可以创建并抛出rust代码的错误使用napi_create_errornapi_throw_error。 每个N-API函数都返回一个napi_status,应该检查这些值。

实际代码

以下示例显示如何安排异步工作。

use nodejs_sys::{
    napi_async_work, napi_callback_info, napi_create_async_work, napi_create_error,
    napi_create_function, napi_create_int64, napi_create_promise, napi_create_string_utf8,
    napi_deferred, napi_delete_async_work, napi_env, napi_get_cb_info, napi_get_value_int64,
    napi_queue_async_work, napi_reject_deferred, napi_resolve_deferred, napi_set_named_property,
    napi_status, napi_value,
};
use std::ffi::c_void;
use std::ffi::CString;

#[derive(Debug, Clone)]
struct Data {
    deferred: napi_deferred,
    work: napi_async_work,
    val: u64,
    result: Option<Result<u64, String>>,
}

pub unsafe extern "C" fn feb(env: napi_env, info: napi_callback_info) -> napi_value {
    let mut buffer: Vec<napi_value> = Vec::with_capacity(1);
    let p = buffer.as_mut_ptr();
    let mut argc = 1 as usize;
    std::mem::forget(buffer);
    napi_get_cb_info(
        env,
        info,
        &mut argc,
        p,
        std::ptr::null_mut(),
        std::ptr::null_mut(),
    );
    let mut start = 0;
    napi_get_value_int64(env, *p, &mut start);
    let mut promise: napi_value = std::mem::zeroed();
    let mut deferred: napi_deferred = std::mem::zeroed();
    let mut work_name: napi_value = std::mem::zeroed();
    let mut work: napi_async_work = std::mem::zeroed();
    let async_name = CString::new("async fibonaci").expect("Error creating string");
    napi_create_string_utf8(env, async_name.as_ptr(), 13, &mut work_name);
    napi_create_promise(env, &mut deferred, &mut promise);
    let v = Data {
        deferred,
        work,
        val: start as u64,
        result: None,
    };
    let data = Box::new(v);
    let raw = Box::into_raw(data);
    napi_create_async_work(
        env,
        std::ptr::null_mut(),
        work_name,
        Some(perform),
        Some(complete),
        std::mem::transmute(raw),
        &mut work,
    );
    napi_queue_async_work(env, work);
    (*raw).work = work;
    promise
}

pub unsafe extern "C" fn perform(_env: napi_env, data: *mut c_void) {
    let mut t: Box<Data> = Box::from_raw(std::mem::transmute(data));
    let mut last = 1;
    let mut second_last = 0;
    for _ in 2..t.val {
        let temp = last;
        last = last + second_last;
        second_last = temp;
    }
    t.result = Some(Ok(last));
    Box::into_raw(task);
}

pub unsafe extern "C" fn complete(env: napi_env, _status: napi_status, data: *mut c_void) {
    let t: Box<Data> = Box::from_raw(std::mem::transmute(data));
    let v = match t.result {
        Some(d) => match d {
            Ok(result) => result,
            Err(_) => {
                let mut js_error: napi_value = std::mem::zeroed();
                napi_create_error(
                    env,
                    std::ptr::null_mut(),
                    std::ptr::null_mut(),
                    &mut js_error,
                );
                napi_reject_deferred(env, t.deferred, js_error);
                napi_delete_async_work(env, t.work);
                return;
            }
        },
        None => {
            let mut js_error: napi_value = std::mem::zeroed();
            napi_create_error(
                env,
                std::ptr::null_mut(),
                std::ptr::null_mut(),
                &mut js_error,
            );
            napi_reject_deferred(env, t.deferred, js_error);
            napi_delete_async_work(env, t.work);
            return;
        }
    };
    let mut obj: napi_value = std::mem::zeroed();
    napi_create_int64(env, v as i64, &mut obj);
    napi_resolve_deferred(env, t.deferred, obj);

    napi_delete_async_work(env, t.work);
}

#[no_mangle]
pub unsafe extern "C" fn napi_register_module_v1(
    env: napi_env,
    exports: napi_value,
) -> nodejs_sys::napi_value {
    let p = CString::new("myFunc").expect("CString::new failed");
    let mut local: napi_value = std::mem::zeroed();
    napi_create_function(
        env,
        p.as_ptr(),
        5,
        Some(feb),
        std::ptr::null_mut(),
        &mut local,
    );
    napi_set_named_property(env, exports, p.as_ptr(), local);
    exports
}

我们创建了一个结构,用于将指针存储到我们napi_async_worknapi_deferred以及我们的输出。 最初,输出是None。 然后我们创建了一个promise,它提供我们的数据中保存的deferred。 我们的所有函数都可以使用此数据。

接下来,我们将数据转换为原始数据并将其传递给napi_create_async_work 函数与其他回调。 我们返回了我们创建的promise,执行,并将数据转换回struct。

在Libuv线程上完成一次perform,从主线程调用complete,以及先前操作和数据的状态。 现在我们可以reject或resolve我们的任务并从队列中删除。

回顾写过的代码

创建一个名为feb的函数,将导出到JavaScript。 此函数将返回promise并调度一个任务在libuv线程池中。

您可以通过使用napi_create_async_work创建promise来实现这一目标,并将两个函数传递给它。 一个是在libuv线程上执行的,另一个在主线程上。

由于您只能从主线程执行javascript,因此您必须仅从主线程resolve或reject promise。 该代码包括大量不安全的函数。

feb 函数

pub unsafe extern "C" fn feb(env: napi_env, info: napi_callback_info) -> napi_value {
    let mut buffer: Vec<napi_value> = Vec::with_capacity(1);
    let p = buffer.as_mut_ptr();
    let mut argc = 1 as usize;
    std::mem::forget(buffer);
// getting arguments for the function
    napi_get_cb_info(
        env,
        info,
        &mut argc,
        p,
        std::ptr::null_mut(),
        std::ptr::null_mut(),
    );
    let mut start = 0;
// converting the napi_value to u64 number
    napi_get_value_int64(env, *p, &mut start);
// promise which would be returned
    let mut promise: napi_value = std::mem::zeroed();
// a pointer to promise to resolve is or reject it
    let mut deferred: napi_deferred = std::mem::zeroed();
// a pointer to our async work name used for debugging
    let mut work_name: napi_value = std::mem::zeroed();
// pointer to async work 
    let mut work: napi_async_work = std::mem::zeroed();
    let async_name = CString::new("async fibonaci").expect("Error creating string");
// creating a string for name
    napi_create_string_utf8(env, async_name.as_ptr(), 13, &mut work_name);
// creating a promise
    napi_create_promise(env, &mut deferred, &mut promise);
    let v = Data {
        deferred,
        work,
        val: start as u64,
        result: None,
    };
// creating a context which can be saved to share state between our functions
    let data = Box::new(v);
// converting it to raw pointer
    let raw = Box::into_raw(data);
// creating the work
    napi_create_async_work(
        env,
        std::ptr::null_mut(),
        work_name,
        Some(perform),
        Some(complete),
        std::mem::transmute(raw),
        &mut work,
    );
// queuing to execute the work
    napi_queue_async_work(env, work);
// setting pointer to work that can be used later
    (*raw).work = work;
// retuning the pormise
    promise
}

perform 函数

pub unsafe extern "C" fn perform(_env: napi_env, data: *mut c_void) {
// getting the shared data and converting the in box
    let mut t: Box<Data> = Box::from_raw(std::mem::transmute(data));
    let mut last = 1;
    let mut second_last = 0;
    for _ in 2..t.val {
        let temp = last;
        last = last + second_last;
        second_last = temp;
    }
// setting the result on shared context
    t.result = Some(Ok(last));
// telling the rust to not to drop the context data
    Box::into_raw(t);
}

complete 函数

pub unsafe extern "C" fn complete(env: napi_env, _status: napi_status, data: *mut c_void) {
// getting the shared context
    let t: Box<Data> = Box::from_raw(std::mem::transmute(data));
    let v = match task.result {
        Some(d) => match d {
            Ok(result) => result,
            Err(_) => {
// if there is error just throw an error
// creating error
                let mut js_error: napi_value = std::mem::zeroed();
                napi_create_error(
                    env,
                    std::ptr::null_mut(),
                    std::ptr::null_mut(),
                    &mut js_error,
                );
// rejecting the promise with error
                napi_reject_deferred(env, task.deferred, js_error);
// deleting the task from the queue
                napi_delete_async_work(env, task.work);
                return;
            }
        },
        None => {
// if no result is found reject with error
// creating an error
            let mut js_error: napi_value = std::mem::zeroed();
            napi_create_error(
                env,
                std::ptr::null_mut(),
                std::ptr::null_mut(),
                &mut js_error,
            );
// rejecting promise with error
            napi_reject_deferred(env, task.deferred, js_error);
// deleting the task from queue
            napi_delete_async_work(env, task.work);
            return;
        }
    };
// creating the number
    let mut obj: napi_value = std::mem::zeroed();
    napi_create_int64(env, v as i64, &mut obj);
// resolving the promise with result
    napi_resolve_deferred(env, t.deferred, obj);
// deleting the work
    napi_delete_async_work(env, t.work);
}

结论

当你涉及N-API处理工作时,这只是冰山一角。 我们列出了一些模式并涵盖了基础知识,例如如何导出函数,创建字符串,数值,数组,对象等JavaScript类型,获取函数的上下文,获取参数和参 函数中的this

我们还的深入研究了如何使用libuv线程示例,并在后台中创建async_work以执行密集型计算。 最后,我们创建并使用了JavaScript的promise,并学习了如何在N-API中处理错误。

如果您不想用手写所有代码,有许多库可用。 比如neonnode-bindgennapi-rs。它们都提供了很好的抽象。