本文主要介绍如何在 Node-API 中实现不同的类型的接口,如:
同步调用
基于 Napi::AsyncWorker 的异步调用,通过回调函数返回
异步调用,返回 Promise
基于 Napi::ThreadSafeFunction 的异步调用,通过回调函数返回
一、同步调用 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
实现异步执行任务,并在其OnOK
或OnError
虚函数中执行JavaScript回调函数返回结果到JavaScript。
以异步计算正整数N的10次方为例,通过回调函数返回计算结果。
与Node.js官方模块的接口一样,回调函数有2个参数callback(err, result),失败err不为Null,成功则err为Null。
继承自Napi::AsyncWorker,重写Execute
虚函数实现任务逻辑,重写OnOK
虚函数执行回调函数返回成功结果,重写OnError
虚函数执行回调函数返回失败。
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实现的异步调用,在本示例中我们使用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