本文主要介绍如何在 Node-API 中实现不同类型的接口,主要有下面几种类型:
同步调用
基于 Napi::AsyncWorker 的异步调用,通过回调函数返回
异步调用,返回 Promise
基于 Napi::ThreadSafeFunction 的异步调用,通过回调函数返回
一、同步调用 同步调用会阻塞 Node.js 的主进程,适合接口能快速返回的场景。
1 2 3 4 5 6 7 8 9 10 11 12 13 Napi::Number Add (const Napi::CallbackInfo& info) { Napi::Env env = info.Env (); if (info.Length () != 2 ) throw Napi::TypeError::New (env, "Wrong number of arguments" ); if (!info[0 ].IsNumber () || !info[1 ].IsNumber ()) throw Napi::TypeError::New (env, "Wrong arguments" ); const int ret = info[0 ].ToNumber ().Int32Value () + info[1 ].ToNumber ().Int32Value (); return Napi::Number::New (env, ret); }
二、异步回调 在 Node-API 中,不能在系统子线程中直接调用 JavaScript 函数,但可以借助 Napi::AsyncWorker
类来实现异步执行任务,在Napi::AsyncWorker 类的 Execute
虚函数中执行具体的任务,并在其 OnOK
或 OnError
虚函数中执行 JavaScript 回调函数,通过回调的方式来返回结果到 JavaScript。
如果在 Execute 函数中需要返回错误,可以调用 SetError 函数来设置错误信息,调用 SetError 函数后,OnError 函数就会被自动执行。需要注意的是,SetError 函数不会终止 Execute 函数的执行流程,通常会在 SetError 调用之后紧接 return 语句。
当 Execute 函数执行完以后,如果期间没有调用 SetError 函数,则 OnOK 函数也会被自动执行。
我们需要在 OnOK 或 OnError 中调用回调函数来将结果返回到 JavaScript,通常保持与 Node.js 官方模块的接口一样(当不是强制的),回调函数有 2 个参数 callback(err, result)
,失败时 err 不为 Null,成功时 err 为 Null。
下面示例以异步方式计算正整数 N 的 10 次方为例,通过回调方式返回计算结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Power10AsyncWorker : public Napi::AsyncWorker { public : Power10AsyncWorker (const Napi::Function& callback, int32_t n) : Napi::AsyncWorker (callback), n_ (n) { } void Execute () override { if (n_ <= 0 ) { SetError ("N must larger than 0" ); return ; } result_ = 1 ; for (int i = 0 ; i < 10 ; i++) { std::this_thread::sleep_for (std::chrono::milliseconds (10 )); result_ *= n_; } } void OnOK () override { Callback ().Call ({Env ().Null (), Napi::Value::From (Env (), result_)}); } void OnError (const Napi::Error& e) override { Callback ().Call ({e.Value (), Env ().Null ()}); } private : int32_t n_ = 0 ; int32_t result_ = 0 ; };
定义接口GetPower10
,支持2个参数:整数N和回调函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void GetPower10 (const Napi::CallbackInfo& info) { Napi::Env env = info.Env (); if (info.Length () != 2 ) throw Napi::TypeError::New (env, "Wrong number of arguments" ); if (!info[0 ].IsNumber () || !info[1 ].IsFunction ()) throw Napi::TypeError::New (env, "Wrong arguments" ); int32_t n = info[0 ].ToNumber ().Int32Value (); Napi::Function callback = info[1 ].As <Napi::Function>(); (new Power10AsyncWorker (callback, n))->Queue (); }
三、返回 Promise 对象 下面示例以异步计算正整数 N 的 20 次方为例,介绍如何返回 Promise 对象。当 N 小于 0 时,Promise 设置为 reject 状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Power20AsyncWorker : public Napi::AsyncWorker { public : Power20AsyncWorker (const Napi::Env& env, const Napi::Promise::Deferred& deferred, int32_t n) : Napi::AsyncWorker (env), deferred_ (deferred), n_ (n) { } void Execute () override { if (n_ <= 0 ) { SetError ("N must larger than 0" ); return ; } result_ = 1 ; for (int i = 0 ; i < 20 ; i++) { std::this_thread::sleep_for (std::chrono::milliseconds (10 )); result_ *= n_; } } void OnOK () override { deferred_.Resolve (Napi::Number::New (Env (), result_)); } void OnError (const Napi::Error& e) override { deferred_.Reject (e.Value ()); } private : Napi::Promise::Deferred deferred_; int32_t n_ = 0 ; int32_t result_ = 0 ; };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Napi::Value GetPower20 (const Napi::CallbackInfo& info) { Napi::Env env = info.Env (); if (info.Length () != 1 ) throw Napi::TypeError::New (env, "Wrong number of arguments" ); if (!info[0 ].IsNumber ()) throw Napi::TypeError::New (env, "Wrong arguments" ); int32_t n = info[0 ].ToNumber ().Int32Value (); Napi::Function callback = info[1 ].As <Napi::Function>(); Napi::Promise::Deferred deferred = Napi::Promise::Deferred::New (env); (new Power20AsyncWorker (env, deferred, n))->Queue (); return deferred.Promise (); }
四、ThreadSafeFunction 前面的示例都没有自己启动系统子线程,而是通过 Napi::AsyncWorker 实现的异步调用,在本示例中我们使用 C++ 11 的 std::thread 开启系统子线程,在子线程中执行任务并调用回调函数返回结果。
要实现在系统子线程中调用 JavaScript 回调函数,需要借助于 Napi::ThreadSafeFunction 类来实现。
下面示例演示了在系统子线程中计算正整数 N 的 30 次方,并通过回调方式返回计算结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 void GetPower30 (const Napi::CallbackInfo& info) { Napi::Env env = info.Env (); if (info.Length () != 2 ) throw Napi::TypeError::New (env, "Wrong number of arguments" ); if (!info[0 ].IsNumber () || !info[1 ].IsFunction ()) throw Napi::TypeError::New (env, "Wrong arguments" ); int32_t n = info[0 ].ToNumber ().Int32Value (); Napi::Function callback = info[1 ].As <Napi::Function>(); std::thread* nativeThread = nullptr ; Napi::ThreadSafeFunction tsfn = Napi::ThreadSafeFunction::New ( env, callback, "Resource Name" , 0 , 1 , [nativeThread](Napi::Env) { if (nativeThread && nativeThread->joinable ()) { nativeThread->join (); } }); nativeThread = new std::thread ([n, tsfn]() { struct CallParam { std::string error; int32_t result = 0 ; }; CallParam* param = new CallParam (); if (n > 0 ) { param->result = 1 ; for (int i = 0 ; i < 30 ; i++) { param->result *= n; } } else { param->error = "N must larger than 0" ; } auto callback = [](Napi::Env env, Napi::Function jsCallback, CallParam* param) { if (jsCallback && param) { try { if (param->error.empty ()) { jsCallback.Call ({env.Null (), Napi::Number::New (env, param->result)}); } else { Napi::Error err = Napi::Error::New (env, param->error); jsCallback.Call ({err.Value (), env.Null ()}); } } catch (std::exception& e) { #if (defined _WIN32 || defined WIN32) OutputDebugStringA (e.what ()); #endif } } if (param) { delete param; } }; napi_status status = tsfn.BlockingCall (param, callback); if (status != napi_ok) { } tsfn.Release (); }); }
完整的示例代码以上传自 Github:
https://github.com/winsoft666/node-addon-sample