# 16.6:NAPI异步编程
笔者在前 5 小节里讲述了在 OpenHarmony 上通过 NAPI 的方式实现了 JS 调用 C++的能力,但是这些实现都是同步的,本节笔者简单介绍一下 NAPI 的异步实现。
# 16.6.1:约定编程规范
ArkUI 开发框架对外提供的 API 命名是需遵守一定规范的,以 @ohos.display
模块提供的 API 为例,源码如下所示:
declare namespace display {
function getDefaultDisplay(callback: AsyncCallback<Display>): void;
function getDefaultDisplay(): Promise<Display>;
function getDefaultDisplaySync(): Display;
}
2
3
4
5
根据该模块提供的方法,根据方法的命名规则可以得出 2
条规范:
同步调用:
- 方法名+ Sync 关键字,如:
getMd5Sync():string
。
- 方法名+ Sync 关键字,如:
异步调用:
- 需要提供 AsyncCallback 和 Promise 的实现,如:
getMd5(): Promise<string>
、getMd5(callback: AsyncCallback<Display>)
。
- 需要提供 AsyncCallback 和 Promise 的实现,如:
因此,我们在 index.d.ts
中声明 NAPI 方法时也按照系统约定的规范来。
# 16.6.2:定义异步方法
笔者在第 5 小结实现了 MD5 的计算,本节笔者把 MD5 的实现放在异步线程中,先在 index.d.ts
声明 JS 侧的方法,如下所示:
export const add: (a: number, b: number) => number;
// 声明异步方法
export function getMd5(value: string, callback: (md5: string) => void): void;
export function getMd5(value: string): Promise<string>;
// 声明同步方法
export function getMd5Sync(value: string): string;
2
3
4
5
6
7
8
getMd5Sync()
表示同步实现 MD5 的计算,getMd5()
表示异步实现 MD5 的调用。
# 16.6.3:实现异步方法
声明完 JS 端的方法后,接着在 hello.cpp
中实现对应的方法,步骤如下:
添加映射
在
hello.cpp
的 Init() 方法里添加 JS 端的方法映射,代码如下所示:static napi_value Init(napi_env env, napi_value exports) { napi_property_descriptor desc[] = { {"add", nullptr, Add, nullptr, nullptr, nullptr, napi_default, nullptr}, {"getMd5Sync", nullptr, GetMd5Sync, nullptr, nullptr, nullptr, napi_default, nullptr}, {"getMd5", nullptr, GetMd5, nullptr, nullptr, nullptr, napi_default, nullptr}, }; napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc); return exports; }
1
2
3
4
5
6
7
8
9
10"getMd5Sync"
和GetMd5Sync
分别表示 JS 端和 C++ 端的方法,通过 napi_define_properties() 把他们映射在一起。方法实现
getMd5() 的 C++ 端代码如下所示:
// 定义异步线程执行中需要的上下文环境 struct Md5Context { // 异步 worker napi_async_work work; // 对应 JS 端的 callback 函数 napi_ref callback; // 对应 JS 端的 promise 对象 napi_deferred promise; // 传递进来的参数 string params; // 计算后的结果 string result; }; // 在子线程中执行 static void doInBackground(napi_env env, void *data) { Md5Context *md5Context = (Md5Context *)data; // 模拟耗时操作,进行 MD5 计算 string md5 = MD5(md5Context->params).toStr(); // 计算后的 MD5 字存储到 result 中 md5Context->result = md5; // 模拟耗时操作,让当前线程休眠 3 秒钟 std::this_thread::sleep_for(std::chrono::seconds(3)); } // 切换到主线程 static void onPostExecutor(napi_env env, napi_status status, void *data) { Md5Context *md5Context = (Md5Context *)data; napi_value returnValue; if (napi_ok != napi_create_string_utf8(env, md5Context->result.c_str(), md5Context->result.length(), &returnValue)) { delete md5Context; md5Context = nullptr; napi_throw_error(env, "-111", "napi_create_string_utf8: error"); return; } if (md5Context->callback) { // 取出缓存的 js 端的 callback napi_value callback; if (napi_ok != napi_get_reference_value(env, md5Context->callback, &callback)) { delete md5Context; md5Context = nullptr; napi_throw_error(env, "-111", "napi_get_reference_value error"); return; } napi_value tempValue; // 调用 callback,把值回调给 JS 端 napi_call_function(env, nullptr, callback, 1, &returnValue, &tempValue); // 删除 callback napi_delete_reference(env, md5Context->callback); } else { // 以 promise 的形式回调数据 if (napi_ok != napi_resolve_deferred(env, md5Context->promise, returnValue)) { delete md5Context; md5Context = nullptr; napi_throw_error(env, "-111", "napi_resolve_deferred error"); } } // 删除异步任务并释放资源 napi_delete_async_work(env, md5Context->work); delete md5Context; md5Context = nullptr; } static napi_value GetMd5(napi_env env, napi_callback_info info) { // 1、从 info 中读取 JS 传递过来的参数放入 args 里 size_t argc = 2; napi_value args[2] = {nullptr}; if (napi_ok != napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)) { napi_throw_error(env, "-1001", "napi_get_cb_info error"); return nullptr; } // 2、读取传入的参数类型 napi_valuetype stringType = napi_undefined; if (napi_ok != napi_typeof(env, args[0], &stringType)) { napi_throw_error(env, "-1002", "napi_typeof string error"); return nullptr; } // 3、传入的 string 如果为 null 或者 undefined 则抛异常 if (napi_null == stringType || napi_undefined == stringType) { napi_throw_error(env, "-1003", "input params null or undefined"); return nullptr; } // 4、读取传入的 string 内容长度 size_t length = 0; if (napi_ok != napi_get_value_string_utf8(env, args[0], nullptr, 0, &length)) { napi_throw_error(env, "-1004", "get string length error"); return nullptr; } // 5、判断传入的 string 长度是否符合 if (0 == length) { napi_throw_error(env, "-1005", "string length can't be zero"); return nullptr; } // 6、读取传入的 string 长度读取内容 char *buffer = new char[length + 1]; if (napi_ok != napi_get_value_string_utf8(env, args[0], buffer, length + 1, &length)) { delete[] buffer; buffer = nullptr; napi_throw_error(env, "-1006", "napi_get_value_string_utf8 string error"); return nullptr; } // 7、读取 JS 有没有传递 callback,如果 callback 为 null 就表示是 promise 的回调方式 napi_valuetype callbackType = napi_undefined; napi_status callbackStatus = napi_typeof(env, args[1], &callbackType); if (napi_ok != callbackStatus && napi_invalid_arg != callbackStatus) { delete[] buffer; buffer = nullptr; napi_throw_error(env, "-1004", "napi_typeof function error"); return nullptr; } // 8、创建一个异步线程需要的数据 model,把传递过来的参数加入进去做下缓存 auto context = new Md5Context(); context->params = buffer; napi_value returnValue = nullptr; // 9、判断是 callback 的回调方式还是 promise 的回调方式 if (napi_function == callbackType) { // 如果是 callback 的回调方式,需要创建 callback 的引用 napi_ref callback; if (napi_ok != napi_create_reference(env, args[1], 1, &callback)) { delete[] buffer; delete context; buffer = nullptr; context = nullptr; napi_throw_error(env, "-11", "napi_create_reference error"); return nullptr; } // 缓存 callback context->callback = callback; // 临时返回一个 undefined 值给 JS 端 napi_get_undefined(env, &returnValue); } else { // promise 的回调方式,创建一个 Promise 的引用 napi_deferred promise; if (napi_ok != napi_create_promise(env, &promise, &returnValue)) { delete[] buffer; delete context; buffer = nullptr; context = nullptr; napi_throw_error(env, "-11", "napi_create_promise error"); return nullptr; } // 缓存 promise context->promise = promise; } napi_value resourceName; if (napi_ok != napi_create_string_utf8(env, "GetMd5", NAPI_AUTO_LENGTH, &resourceName)) { delete[] buffer; delete context; buffer = nullptr; context = nullptr; napi_throw_error(env, "-11", "napi_create_string_utf8 resourceName error"); return nullptr; } // 10、创建一个异步任务 napi_async_work asyWork; napi_status status = napi_create_async_work(env, nullptr, resourceName, doInBackground, onPostExecutor, (void *)context, &asyWork); if (napi_ok != status) { delete[] buffer; delete context; buffer = nullptr; context = nullptr; napi_throw_error(env, "-11", "napi_create_async_work error"); return nullptr; } // 11、保存异步任务 context->work = asyWork; // 12、添加进异步队列 napi_queue_async_work(env, asyWork); return returnValue; }
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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204getMd5() 的代码比较多,笔者添加的注释比较清楚,前 6 个小步骤是对传递进来的参数做基础校验,第 7 步是根据参数判断当前异步执行的回调方式是 Promise 还是 Callback。第 8 步创建了一个
Md5Context
对象,它的作用是把当前相关参数缓存下来目的是接下来在异步线程里使用这些参数,第 9 步根据异步回调的方法创建 Promise 或者 Callback 然后把他们保存在Md5Context
对象里。第 10 步创建一个异步任务,然后把异步任务添加进异步队列中。napi_create_async_work() 方法的第 3 、 4 个参数需要注意,doInBackground() 方法是在异步线程中执行的,onPostExecutor() 方法在异步线程结束后切换到主线程中执行。
完整代码
hello.cpp
全部代码如下所示:#include <cstddef> #include <cstring> #include "napi/native_api.h" #include <js_native_api.h> #include <js_native_api_types.h> #include <node_api.h> #include <node_api_types.h> #include <string> #include <thread> #include "./md5/md5.h" // 定义异步线程执行中需要的上下文环境 struct Md5Context { // 异步 worker napi_async_work work; // 对应 JS 端的 callback 函数 napi_ref callback; // 对应 JS 端的 promise 对象 napi_deferred promise; // 传递进来的参数 string params; // 计算后的结果 string result; }; static void doInBackground(napi_env env, void *data) { Md5Context *md5Context = (Md5Context *)data; // 模拟耗时操作,进行 MD5 计算 string md5 = MD5(md5Context->params).toStr(); // 计算后的 MD5 字存储到 result 中 md5Context->result = md5; // 模拟耗时操作,让当前线程休眠 3 秒钟 std::this_thread::sleep_for(std::chrono::seconds(3)); } static void onPostExecutor(napi_env env, napi_status status, void *data) { Md5Context *md5Context = (Md5Context *)data; napi_value returnValue; if (napi_ok != napi_create_string_utf8(env, md5Context->result.c_str(), md5Context->result.length(), &returnValue)) { delete md5Context; md5Context = nullptr; napi_throw_error(env, "-111", "napi_create_string_utf8: error"); return; } if (md5Context->callback) { // 取出缓存的 js 端的 callback napi_value callback; if (napi_ok != napi_get_reference_value(env, md5Context->callback, &callback)) { delete md5Context; md5Context = nullptr; napi_throw_error(env, "-111", "napi_get_reference_value error"); return; } napi_value tempValue; // 调用 callback,把值回调给 JS 端 napi_call_function(env, nullptr, callback, 1, &returnValue, &tempValue); // 删除 callback napi_delete_reference(env, md5Context->callback); } else { // 以 promise 的形式回调数据 if (napi_ok != napi_resolve_deferred(env, md5Context->promise, returnValue)) { delete md5Context; md5Context = nullptr; napi_throw_error(env, "-111", "napi_resolve_deferred error"); } } // 删除异步任务并释放资源 napi_delete_async_work(env, md5Context->work); delete md5Context; md5Context = nullptr; } static napi_value GetMd5(napi_env env, napi_callback_info info) { // 1、从 info 中读取 JS 传递过来的参数放入 args 里 size_t argc = 2; napi_value args[2] = {nullptr}; if (napi_ok != napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)) { napi_throw_error(env, "-1001", "napi_get_cb_info error"); return nullptr; } // 2、读取传入的参数类型 napi_valuetype stringType = napi_undefined; if (napi_ok != napi_typeof(env, args[0], &stringType)) { napi_throw_error(env, "-1002", "napi_typeof string error"); return nullptr; } // 3、传入的 string 如果为 null 或者 undefined 则抛异常 if (napi_null == stringType || napi_undefined == stringType) { napi_throw_error(env, "-1003", "input params null or undefined"); return nullptr; } // 4、读取传入的 string 内容长度 size_t length = 0; if (napi_ok != napi_get_value_string_utf8(env, args[0], nullptr, 0, &length)) { napi_throw_error(env, "-1004", "get string length error"); return nullptr; } // 5、判断传入的 string 长度是否符合 if (0 == length) { napi_throw_error(env, "-1005", "string length can't be zero"); return nullptr; } // 6、读取传入的 string 长度读取内容 char *buffer = new char[length + 1]; if (napi_ok != napi_get_value_string_utf8(env, args[0], buffer, length + 1, &length)) { delete[] buffer; buffer = nullptr; napi_throw_error(env, "-1006", "napi_get_value_string_utf8 string error"); return nullptr; } // 7、读取 JS 有没有传递 callback,如果 callback 为 null 就表示是 promise 的回调方式 napi_valuetype callbackType = napi_undefined; napi_status callbackStatus = napi_typeof(env, args[1], &callbackType); if (napi_ok != callbackStatus && napi_invalid_arg != callbackStatus) { delete[] buffer; buffer = nullptr; napi_throw_error(env, "-1004", "napi_typeof function error"); return nullptr; } // 8、创建一个异步线程需要的数据 model,把传递过来的参数加入进去做下缓存 auto context = new Md5Context(); context->params = buffer; napi_value returnValue = nullptr; // 9、判断是 callback 的回调方式还是 promise 的回调方式 if (napi_function == callbackType) { // 如果是 callback 的回调方式,需要创建 callback 的引用 napi_ref callback; if (napi_ok != napi_create_reference(env, args[1], 1, &callback)) { delete[] buffer; delete context; buffer = nullptr; context = nullptr; napi_throw_error(env, "-11", "napi_create_reference error"); return nullptr; } // 缓存 callback context->callback = callback; // 临时返回一个 undefined 值给 JS 端 napi_get_undefined(env, &returnValue); } else { // promise 的回调方式,创建一个 Promise 的引用 napi_deferred promise; if (napi_ok != napi_create_promise(env, &promise, &returnValue)) { delete[] buffer; delete context; buffer = nullptr; context = nullptr; napi_throw_error(env, "-11", "napi_create_promise error"); return nullptr; } // 缓存 promise context->promise = promise; } napi_value resourceName; if (napi_ok != napi_create_string_utf8(env, "GetMd5", NAPI_AUTO_LENGTH, &resourceName)) { delete[] buffer; delete context; buffer = nullptr; context = nullptr; napi_throw_error(env, "-11", "napi_create_string_utf8 resourceName error"); return nullptr; } // 10、创建一个异步任务 napi_async_work asyWork; napi_status status = napi_create_async_work(env, nullptr, resourceName, doInBackground, onPostExecutor, (void *)context, &asyWork); if (napi_ok != status) { delete[] buffer; delete context; buffer = nullptr; context = nullptr; napi_throw_error(env, "-11", "napi_create_async_work error"); return nullptr; } // 11、保存异步任务 context->work = asyWork; // 12、添加进异步队列 napi_queue_async_work(env, asyWork); return returnValue; } static napi_value GetMd5Sync(napi_env env, napi_callback_info info) { // 1、从info中取出JS传递过来的参数放入args size_t argc = 1; napi_value args[1] = {nullptr}; if (napi_ok != napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)) { napi_throw_error(env, "-1000", "napi_get_cb_info error"); return nullptr; } // 2、获取参数的类型 napi_valuetype stringType; if (napi_ok != napi_typeof(env, args[0], &stringType)) { napi_throw_error(env, "-1001", "napi_typeof error"); return nullptr; } // 3、如果参数为null或者undefined,则抛异常 if (napi_null == stringType || napi_undefined == stringType) { napi_throw_error(env, "-1002", "the param can't be null"); return nullptr; } // 4、获取传递的string长度 size_t length = 0; if (napi_ok != napi_get_value_string_utf8(env, args[0], nullptr, 0, &length)) { napi_throw_error(env, "-1003", "napi_get_value_string_utf8 error"); return nullptr; } // 5、如果传递的是"",则抛异常 if (length == 0) { napi_throw_error(env, "-1004", "the param length invalid"); return nullptr; } // 6、读取传递的string参数放入buffer中 char *buffer = new char[length + 1]; if (napi_ok != napi_get_value_string_utf8(env, args[0], buffer, length + 1, &length)) { delete[] buffer; buffer = nullptr; napi_throw_error(env, "-1005", "napi_get_value_string_utf8 error"); return nullptr; } // 7、计算MD5加密操作 std::string str = buffer; str = MD5(str).toStr(); // 8、把C++数据转成napi_value并返回 napi_value value = nullptr; const char *md5 = str.c_str(); if (napi_ok != napi_create_string_utf8(env, md5, strlen(md5), &value)) { delete[] buffer; buffer = nullptr; napi_throw_error(env, "-1006", "napi_create_string_utf8 error"); return nullptr; } // 9、资源清理 delete[] buffer; buffer = nullptr; return value; } static napi_value Add(napi_env env, napi_callback_info info) { size_t requireArgc = 2; size_t argc = 2; napi_value args[2] = {nullptr}; napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); napi_valuetype valuetype0; napi_typeof(env, args[0], &valuetype0); napi_valuetype valuetype1; napi_typeof(env, args[1], &valuetype1); double value0; napi_get_value_double(env, args[0], &value0); double value1; napi_get_value_double(env, args[1], &value1); napi_value sum; napi_create_double(env, value0 + value1, &sum); return sum; } EXTERN_C_START static napi_value Init(napi_env env, napi_value exports) { napi_property_descriptor desc[] = { {"add", nullptr, Add, nullptr, nullptr, nullptr, napi_default, nullptr}, {"getMd5Sync", nullptr, GetMd5Sync, nullptr, nullptr, nullptr, napi_default, nullptr}, {"getMd5", nullptr, GetMd5, nullptr, nullptr, nullptr, napi_default, nullptr}, }; napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc); return exports; } EXTERN_C_END static napi_module demoModule = { .nm_version = 1, .nm_flags = 0, .nm_filename = nullptr, .nm_register_func = Init, .nm_modname = "entry", .nm_priv = ((void *)0), .reserved = {0}, }; extern "C" __attribute__((constructor)) void RegisterEntryModule(void) { napi_module_register(&demoModule); }
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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332Index.ets
的测试代码如下:import testNapi from 'libentry.so'; @Entry @Component struct Index { @State message: string = 'Hello,OpenHarmony' build() { Column({ space: 10 }) { Text(this.message) .fontSize(20) Button("同步回调") .onClick(() => { this.message = testNapi.getMd5Sync("Hello, OpenHarmony") }) Button("异步 Callback 回调") .onClick(() => { this.message = "计算中..."; testNapi.getMd5("Hello, OpenHarmony", (md5: string) => { this.message = md5; }); }) Button("异步 Promise 回调") .onClick(() => { this.message = "计算中..."; testNapi.getMd5("Hello, OpenHarmony").then((md5: string) => { this.message = md5; }).catch((error: Error) => { this.message = "error: " + error; }) }) } .padding(10) .width('100%') .height("100%") } }
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样例运行结果如下图所示:
# 16.6.4:小结
本节笔者简单讲述了 NAPI 的异步实现方式,下一小节笔者从源码的角度给大家讲解一下 NAPI 的实现原理,敬请期待……