本文主要介绍如何在 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实现异步执行任务,并在其OnOKOnError虚函数中执行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) {
// N小于0则返回错误
SetError("N must larger than 0");
return;
}

result_ = 1;
for (int i = 0; i < 10; i++) {
// 每次暂停10ms,模拟耗时操作
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) {
// 小于0时返回错误
SetError("N must larger than 0");
return;
}

result_ = 1;
for (int i = 0; i < 20; i++) {
// 每次暂停10ms,模拟耗时操作
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", // Name
0, // Unlimited queue
1, // Only one thread will use this initially
[nativeThread](Napi::Env) { // Finalizer used to clean threads up
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) {
// TODO How to handle error
}

tsfn.Release();
});
}

完整的示例代码以上传自Github:

https://github.com/winsoft666/node-addon-sample