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实现一个简单的图像滤镜(如反色)。

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

相关推荐
张愚歌几秒前
快速上手Leaflet:轻松创建你的第一个交互地图
前端
唐某人丶8 分钟前
教你如何用 JS 实现 Agent 系统(3)—— 借鉴 Cursor 的设计模式实现深度搜索
前端·人工智能·aigc
看到我请叫我铁锤13 分钟前
vue3使用leaflet的时候高亮显示省市区
前端·javascript·vue.js
南囝coding22 分钟前
Vercel 发布 AI Gateway 神器!可一键访问数百个模型,助力零门槛开发 AI 应用
前端·后端
AI大模型29 分钟前
前端学 AI 不用愁!手把手教你用 LangGraph 实现 ReAct 智能体(附完整流程 + 代码)
前端·llm·agent
小红1 小时前
网络通信基石:从IP地址到子网划分的完整指南
前端·网络协议
一枚前端小能手1 小时前
🔥 前端储存这点事 - 5个存储方案让你的数据管理更优雅
前端·javascript
willlzq1 小时前
深入探索Swift的Subscript机制和最佳实践
前端
RockerLau1 小时前
micro-zoe子应用路由路径污染问题
前端
代码代码快快显灵1 小时前
Axios的基本知识点以及vue的开发工程(基于大事件)详细解释
前端·javascript·vue.js