Node-API 学习二

很好!既然你已经掌握了基础的 Node-API 使用,接下来我们继续深入,学习更高级的功能。我会分步骤带你掌握 ​异步操作线程安全 ​ 和 ​高性能 Buffer 处理,这些都是实际开发中常用的技术。


1. 异步操作(Async Work)​

目标​:让 C++ 执行耗时计算(如 1 秒),然后返回结果给 JS,不阻塞 JS 主线程。

​**修改 hello.cc**​

scss 复制代码
#include <thread> // 用于 sleep
#include <chrono>

// 1. 定义异步任务的数据结构
struct AsyncData {
    napi_async_work work;   // 异步任务
    napi_deferred deferred; // 用于 Promise 的 resolve/reject
    napi_env env;           // 当前环境
    double input;           // JS 传入的参数
    double result;          // 计算结果
};

// 2. 执行耗时计算的函数(在子线程运行)
void ExecuteWork(napi_env env, void* data) {
    AsyncData* async_data = static_cast<AsyncData*>(data);
    std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟耗时操作
    async_data->result = async_data->input * 2;          // 计算
}

// 3. 计算完成后的回调(回到 JS 线程)
void CompleteWork(napi_env env, napi_status status, void* data) {
    AsyncData* async_data = static_cast<AsyncData*>(data);

    // 创建返回结果
    napi_value result;
    napi_create_double(env, async_data->result, &result);

    // 通知 JS Promise 已完成
    napi_resolve_deferred(env, async_data->deferred, result);

    // 清理资源
    napi_delete_async_work(env, async_data->work);
    delete async_data;
}

// 4. JS 调用的入口函数
napi_value AsyncCompute(napi_env env, napi_callback_info info) {
    // 获取 JS 传入的参数
    size_t argc = 1;
    napi_value args[1];
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    double input;
    napi_get_value_double(env, args[0], &input);

    // 创建 Promise 和异步数据
    napi_value promise;
    AsyncData* async_data = new AsyncData();
    async_data->env = env;
    async_data->input = input;
    napi_create_promise(env, &async_data->deferred, &promise);

    // 创建异步任务
    napi_value resource_name;
    napi_create_string_utf8(env, "AsyncCompute", NAPI_AUTO_LENGTH, &resource_name);
    napi_create_async_work(env, nullptr, resource_name, 
                          ExecuteWork, CompleteWork, 
                          async_data, &async_data->work);
    napi_queue_async_work(env, async_data->work);

    return promise; // 返回 Promise 给 JS
}

// 5. 在 Init 里注册 AsyncCompute
napi_value Init(napi_env env, napi_value exports) {
    // ...(之前的代码)
    napi_create_function(env, nullptr, 0, AsyncCompute, nullptr, &fn);
    napi_set_named_property(env, exports, "asyncCompute", fn);
    return exports;
}

​**修改 index.js**​

javascript 复制代码
console.log("开始计算...");
addon.asyncCompute(10).then(result => {
    console.log("计算结果:", result); // 2 秒后输出 20
});
console.log("JS 主线程未被阻塞!");

运行

复制代码
node-gyp rebuild
node index.js

你会看到:

erlang 复制代码
开始计算...
JS 主线程未被阻塞!
(1 秒后)计算结果: 20

关键点

  • ExecuteWork在子线程运行,不会阻塞 JS。
  • CompleteWork回到 JS 线程,可以安全调用 Node-API。
  • 使用 Promise让 JS 可以 await.then()获取结果。

2. 线程安全(Thread-safe Function)​

目标​:在 C++ 子线程中,每隔 1 秒向 JS 发送进度信息。

​**修改 hello.cc**​

scss 复制代码
// 1. 定义线程安全回调的数据结构
struct ProgressData {
    napi_threadsafe_function tsfn; // 线程安全函数
    int progress;                  // 当前进度
};

// 2. 子线程的工作函数
void BackgroundThread(ProgressData* data) {
    for (int i = 0; i <= 100; i += 10) {
        data->progress = i;
        
        // 调用 JS 回调(非阻塞方式)
        napi_call_threadsafe_function(
            data->tsfn, 
            nullptr,  // 无额外数据
            napi_tsfn_nonblocking
        );
        
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
    
    // 结束线程安全函数
    napi_release_threadsafe_function(data->tsfn, napi_tsfn_release);
    delete data;
}

// 3. JS 调用的入口函数
napi_value StartProgress(napi_env env, napi_callback_info info) {
    // 获取 JS 回调函数
    size_t argc = 1;
    napi_value args[1];
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    
    // 创建线程安全函数
    ProgressData* data = new ProgressData();
    napi_create_threadsafe_function(
        env,
        args[0],           // JS 回调函数
        nullptr,           // 无 this 绑定
        napi_create_string_utf8(env, "Progress", NAPI_AUTO_LENGTH),
        0, 1, nullptr,
        [](napi_env env, void* finalize_data, void* hint) {}, // 清理函数
        &data->tsfn
    );
    
    // 启动子线程
    std::thread(BackgroundThread, data).detach();
    
    return nullptr;
}

// 4. 在 Init 里注册 StartProgress
napi_value Init(napi_env env, napi_value exports) {
    // ...(之前的代码)
    napi_create_function(env, nullptr, 0, StartProgress, nullptr, &fn);
    napi_set_named_property(env, exports, "startProgress", fn);
    return exports;
}

​**修改 index.js**​

javascript 复制代码
addon.startProgress((progress) => {
    console.log("当前进度:", progress);
});

运行

复制代码
node-gyp rebuild
node index.js

你会看到每隔 1 秒输出:

makefile 复制代码
当前进度: 0
当前进度: 10
...
当前进度: 100

关键点

  • napi_threadsafe_function允许子线程安全调用 JS 函数。
  • napi_call_threadsafe_function是非阻塞的,适合高频回调。
  • 结束时必须 napi_release_threadsafe_function

3. 高性能 Buffer 处理

目标 ​:在 C++ 中直接操作 JS 的 ArrayBuffer,避免数据拷贝。

​**修改 hello.cc**​

scss 复制代码
// 1. 处理 ArrayBuffer 的函数
napi_value ProcessBuffer(napi_env env, napi_callback_info info) {
    size_t argc = 1;
    napi_value args[1];
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    
    // 检查是否为 ArrayBuffer
    bool is_arraybuffer;
    napi_is_arraybuffer(env, args[0], &is_arraybuffer);
    if (!is_arraybuffer) {
        napi_throw_error(env, nullptr, "参数必须是 ArrayBuffer");
        return nullptr;
    }
    
    // 获取 ArrayBuffer 的数据指针
    void* data;
    size_t length;
    napi_get_arraybuffer_info(env, args[0], &data, &length);
    
    // 直接修改数据(无需拷贝)
    uint8_t* bytes = static_cast<uint8_t*>(data);
    for (size_t i = 0; i < length; i++) {
        bytes[i] += 1; // 每个字节 +1
    }
    
    return args[0]; // 返回修改后的 ArrayBuffer
}

// 2. 在 Init 里注册 ProcessBuffer
napi_value Init(napi_env env, napi_value exports) {
    // ...(之前的代码)
    napi_create_function(env, nullptr, 0, ProcessBuffer, nullptr, &fn);
    napi_set_named_property(env, exports, "processBuffer", fn);
    return exports;
}

​**修改 index.js**​

ini 复制代码
const buffer = new ArrayBuffer(4);
const view = new Uint8Array(buffer);
view[0] = 1;
view[1] = 2;
view[2] = 3;
view[3] = 4;

addon.processBuffer(buffer);
console.log(view); // 输出: Uint8Array [ 2, 3, 4, 5 ]

运行

复制代码
node-gyp rebuild
node index.js

你会看到 Uint8Array的值被 C++ 直接修改了。

关键点

  • napi_get_arraybuffer_info获取原始指针,避免数据拷贝。
  • 直接操作内存比 napi_set_element快 100 倍以上。
  • 适用于图像处理、音视频编解码等高性能场景。

总结

技术 适用场景 关键 API
异步操作 耗时计算(文件 I/O、网络请求) napi_create_async_work, napi_queue_async_work
线程安全 子线程实时通知 JS(如进度更新) napi_threadsafe_function, napi_call_threadsafe_function
Buffer 处理 高性能数据操作(图像/音频处理) napi_get_arraybuffer_info, 直接内存操作

下一步建议

  1. 尝试用 asyncCompute实现一个文件哈希计算器。
  2. startProgress做一个真实的进度条(如文件下载)。
  3. processBuffer实现一个简单的图像滤镜(如反色)。

如果有任何问题,欢迎随时问我! 😊

相关推荐
whysqwhw3 小时前
Node-API 学习一
前端
Jenna的海糖4 小时前
Vue 中 v-model 的 “双向绑定”:从原理到自定义组件适配
前端·javascript·vue.js
一碗清汤面4 小时前
打造AI代码审查员:使用 Gemini + Git Hooks 自动化 Code Review
前端·git·代码规范
Sagittarius_A*4 小时前
SpringBoot Web 入门指南:从零搭建第一个SpringBoot程序
java·前端·spring boot·后端
我是ed4 小时前
# Vue 前端封装组件基础知识点
前端
芦苇Z4 小时前
CSS :has() 父级选择器与关系查询
前端·css
前端康师傅4 小时前
Javascript 中循环的使用
前端·javascript
毕了业就退休4 小时前
从 WebSocket 转向 SSE:轻量实时推送的另一种选择
前端·javascript·https
子兮曰4 小时前
🚀 图片加载速度提升300%!Vue/React项目WebP兼容方案大揭秘
前端·vue.js·react.js