对IO、CPU密集型任务需要异步处理。 NAPI支持异步模型,提供了Promise、Callback 2种方式。
- CPU密集型程序适合C语言多线程,I/O密集型适合脚本语言开发的多线程。
写在开头 :
- 本文在[三方库移植之NAPI开发[1]—Hello OpenHarmony NAPI]的基础上修改hellonapi.cpp、index.ets,接着学习NAPI异步模型的Promise、Callback方式。
- 本文共有三个示例,分别是
Callback 异步接口
示例、Promise 异步接口
示例、规范异步接口
示例。在本文末尾的资源中提供了这三个示例的源代码,读者可以下载在开发板上运行。
- 开发基于最新的OpenHarmony3.2Beta3版本及API9,标准系统开发板为润和软件DAYU200。
NAPI异步方式实现原理
同步方式,所有的代码处理都在原生方法(主线程)中完成。
异步方式,所有的代码处理在多个线程中完成。
1)立即返回一个临时结果给js调用者
2)另起线程完成异步业务逻辑的执行
3)通过callback或promise返回真正的结果
-
异步工作项工作时序图 :
-
原生方法被调用时,原生方法完成数据接收
、数据类型转换
、存入上下文数据
,之后创建异步工作项
-
异步工作项
会加入调度队列,由异步工作线程池
统一调度,原生方法返回空值(Callback方式)或返回Promise对象(Promise方式)。
-
异步方式依赖NAPI框架提供的napi_create_async_work()
函数创建异步工作项
napi_create_async_work()在foundation/arkui/napi/native_engine/native_node_api.cpp第71行
NAPI_EXTERN napi_status napi_create_async_work(napi_env env,
napi_value async_resource,
napi_value async_resource_name,
napi_async_execute_callback execute,
napi_async_complete_callback complete,
void* data,
napi_async_work* result)
参数说明:
[in] env: 传入接口调用者的环境,包含js引擎等,由框架提供,默认情况下直接传入即可。
[in] async_resource: 可选项,关联async_hooks。
[in] async_resource_name: 异步资源标识符,主要用于async_hooks API暴露断言诊断信息。
[in] execute: 执行业务逻辑计算函数,由worker线程池调度执行。在该函数中执行IO、CPU密集型任务,不阻塞主线程。
[in] complete: execute参数指定的函数执行完成或取消后,触发执行该函数。此函数在EventLoop线程中执行。
[in] data: 用户提供的上下文数据,用于传递数据。
[out] result: napi_async_work*指针,用于返回当前此处函数调用创建的异步工作项。 返回值:返回napi_ok表示转换成功,其他值失败。
napi_create_async_work里有两个回调:
- execute
- execute函数用于执行工作项的业务逻辑,异步工作项被调度后,该函数从上下文数据中获取输入数据,在worker线程中完成
业务逻辑计算
(不阻塞主线程)并将结果写入上下文数据。
- 因为execute函数不在JS线程中,所以不允许execute函数调用napi的接口。业务逻辑的返回值可以返回到complete回调中处理。
- complete
- 业务逻辑处理execute函数执行完成或被取消后,触发EventLoop执行complete函数,complete函数从上下文数据中获取结果,转换为JS类型,调用
JS回调函数
或通过Promise resolve()
返回结果。
- 可以调用napi的接口,将execute中的返回值封装成JS对象返回。此回调在JS线程中执行。
- 管理简单的异步操作的方法还有这些
-
napi_delete_async_work(napi_env env, napi_async_work work)
删除异步工作线程
-
napi_queue_async_work(napi_env env, napi_async_work work)
将刚创建的异步工作项加到队列(排队),由底层去调度执行
-
napi_cancel_async_work(napi_env env, napi_async_work work)
取消异步工作项
NAPI支持异步模型
- OpenHarmony标准系统异步接口实现支持Promise方式和Callback方式。NAPI支持异步模型,提供了Promise、Callback方式。
- 标准系统异步接口实现规范要求,若引擎开启Promise特性支持,则异步方法必须同时支持Callback方式和Promise方式。
- 由应用开发者决定使用哪种方式,通过是否传递Callback函数区分异步方法是Callback方式还是Promise方式
- 不传递Callback即为Promise方式(方法执行结果为Promise实例对象),否则为Callback方式
- Promise、Callback 异步模型都是 OHOS 标准异步模型。
- Callback异步模型
- 用户在调用接口的时候,接口实现将异步执行任务
- 任务执行结果以参数的形式提供给用户注册的回调函数,这些参数的第一个是 Error 或 undefined 类型,分别表示执行出错与正常。
- Promise异步模型
- 对象的状态不受外界影响;
- 一旦状态改变了就不会再变,也就是说任何时候Promise都只有一种状态。
- ES6原生提供了Promise对象,Promise是异步编程的一种解决方案,可以替代传统的解决方案回调函数和事件;
- promise对象是一个异步操作的结果,提供了一些API使得异步执行可以按照同步的流表示出来,避免了层层嵌套的回调函数,保证了回调是以异步的方式进行调用的;
- 用户在调用这些接口的时候,接口实现将异步执行任务,同时返回一个 Promise 对象,其代表异步操作的结果;
- 在返回的结果的个数超过一个时,其以对象属性的形式返回。
- ES6:全称ECMAScript 6.0。ECMAScript 是JavaScript语言的国际标准,JavaScript是ECMAScript的实现。
Callback 异步接口
Callback 异步接口示例代码
hellonapi.cpp文件
#include <string.h>
#include <stdio.h>
#include "napi/native_node_api.h"
#include "napi/native_api.h"
struct AddonData {
napi_async_work asyncWork = nullptr;
napi_deferred deferred = nullptr;
napi_ref callback = nullptr;
double args[2] = {0};
double result = 0;
};
static void addExecuteCB(napi_env env, void *data) {
AddonData *addonData = (AddonData *)data;
addonData->result = addonData->args[0] + addonData->args[1];
}
static void addCallbackCompleteCB(napi_env env, napi_status status, void *data) {
AddonData *addonData = (AddonData *)data;
napi_value callback = nullptr;
napi_get_reference_value(env, addonData->callback, &callback);
napi_value undefined = nullptr;
napi_get_undefined(env, &undefined);
napi_value result = nullptr;
napi_create_double(env, addonData->result, &result);
napi_value callbackResult = nullptr;
napi_call_function(env, undefined, callback, 1, &result, &callbackResult);
if (addonData->callback != nullptr) {
napi_delete_reference(env, addonData->callback);
}
napi_delete_async_work(env, addonData->asyncWork);
delete addonData;
}
static napi_value addCallback(napi_env env, napi_callback_info info) {
size_t argc = 3;
napi_value args[3];
napi_value thisArg = nullptr;
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, &thisArg, nullptr));
napi_valuetype valuetype0;
NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0));
napi_valuetype valuetype1;
NAPI_CALL(env, napi_typeof(env, args[1], &valuetype1));
if (valuetype0 != napi_number || valuetype1 != napi_number) {
napi_throw_type_error(env, nullptr, "Wrong arguments. 2 numbers expected.");
return NULL;
}
napi_valuetype valuetype2;
NAPI_CALL(env, napi_typeof(env, args[2], &valuetype2));
if (valuetype2 != napi_function) {
napi_throw_type_error(env, nullptr, "Callback function expected.");
return NULL;
}
auto addonData = new AddonData{
.asyncWork = nullptr,
};
NAPI_CALL(env, napi_get_value_double(env, args[0], &addonData->args[0]));
NAPI_CALL(env, napi_get_value_double(env, args[1], &addonData->args[1]));
NAPI_CALL(env, napi_create_reference(env, args[2], 1, &addonData->callback));
napi_value resourceName = nullptr;
napi_create_string_utf8(env, "addCallback", NAPI_AUTO_LENGTH, &resourceName);
napi_create_async_work(env, nullptr, resourceName, addExecuteCB, addCallbackCompleteCB, (void *)addonData,
&addonData->asyncWork);
napi_queue_async_work(env, addonData->asyncWork);
napi_value result = 0;
NAPI_CALL(env, napi_get_null(env, &result));
return result;
}
static napi_value registerFunc(napi_env env, napi_value exports) {
static napi_property_descriptor desc[] = {
DECLARE_NAPI_FUNCTION("addCallback", addCallback),
};
NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc));
return exports;
}
static napi_module hellonapiModule = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
.nm_register_func = registerFunc,
.nm_modname = "hellonapi",
.nm_priv = ((void*)0),
.reserved = { 0 },
};
extern "C" __attribute__((constructor)) void hellonapiModuleRegister()
{
napi_module_register(&hellonapiModule);
}
index.ets
import prompt from '@system.prompt';
import hellonapi from '@ohos.hellonapi'
@Entry
@Component
struct TestAdd {
build() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Button("hellonapi.addCallback(x, y, callback)").margin(10).fontSize(20).onClick(() => {
let num1 = 123, num2 = 456
hellonapi.addCallback(num1, num2, (result) => {
prompt.showToast({ message: `hellonapi.addCallback(${num1}, ${num2}) = ${result}` })
})
})
}
.width('100%')
.height('100%')
}
}
@ohos.hellonapi.d.ts
declare namespace hellonapi {
function addCallback(num1: number, num2: number, callback:(result: number) => void): void;
}
export default hellonapi;
主线程:获取JS传入参数
获取JS传入参数
在异步工作项工作时序图中位置,在图中用红框标记如下
napi_valuetype valuetype0;
NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0));
napi_valuetype valuetype1;
NAPI_CALL(env, napi_typeof(env, args[1], &valuetype1));
if (valuetype0 != napi_number || valuetype1 != napi_number) {
napi_throw_type_error(env, nullptr, "Wrong arguments. 2 numbers expected.");
return NULL;
}
napi_valuetype valuetype2;
NAPI_CALL(env, napi_typeof(env, args[2], &valuetype2));
if (valuetype2 != napi_function) {
napi_throw_type_error(env, nullptr, "Callback function expected.");
return NULL;
}
- 使用napi_typeof接口进行参数类型的判断
- NAPI_CALL()是用来调用NAPI中的API的。
主线程:初始化上下文数据
初始化上下文数据
在异步工作项工作时序图中位置,在图中用红框标记如下
- 异步方法需要在不同线程中传递各种
业务数据
(上下文数据
),就需要定义一个结构体保存这些被传递的信息。用于在主线程方法、Work线程、EventLoop线程之间传递数据。
struct 结构体名(也就是可选标记名){ 成员变量;};//使用分号;表示定义结束。
- 本示例定义的上下文数据包含:
异步工作项对象
、回调函数
、2个参数(加数、被加数
)、业务逻辑处理结果
等4个属性。
struct AddonData {
napi_async_work asyncWork = nullptr;
napi_ref callback = nullptr;
double args[2] = {0};
double result = 0;
};
- OpenHarmony的NAPI框架将ECMAScript标准中定义的Boolean、Null、Undefined、Number、BigInt、String、Symbol和Object八种
数据类型
和Function类型
,都已统一封装为napi_value类型,故可如获取数据类型
的参数一样获取Function类型
的参数。
Function是JavaScript提供的一种引用类型,通过Function类型创建Function对象。
在JavaScript中,函数也是以对象的形式存在的,每个函数都是一个Function对象。
- 定义好结构体后,接着我们将接收到的3个参数(
加数
、被加数
、回调函数
)转换存入上下文数据完成初始化上下文数据
,
- number类型的(
加数
、被加数
)转换为double直接存入。
- Function类型的参数(
回调函数
)怎么处理?不能直接存入napi_value类型。
- 因为牵涉到NAPI对象生命周期管理问题。napi_value类型引用对象的生命周期在原生方法退出后结束,后面在work线程无法获取其值。
- NAPI提供了一种生命期限长于原生方法的对象引用类型—— napi_ref,所以调用napi_create_reference()函数将接收到的napi_value类型的回调函数参数callback转换为napi_ref类型。napi_create_reference()函数定义如下:
NAPI_EXTERN napi_status napi_create_reference(napi_env env,
napi_value value,
uint32_t initial_refcount,
napi_ref* result);
参数说明:
[in] env: 传入接口调用者的环境,包含js引擎等,由框架提供,默认情况下直接传入即可。
[in] value: 需要创建一个引用的napi_value对象
[in] initial_refcount: 初始化引用次数。
[out] result: 指针,指向新创建的napi_ref对象。 返回值:返回napi_ok表示转换成功,其他值失败。
- napi_ref引用对象在原生方法退出后不自动回收,由用户管理napi_ref类型对象的生命周期。
- 用户管理napi_ref类型对象的生命周期的方法有
- napi_create_reference() : 将napi_value包装成napi_ref引用对象
- napi_get_reference_value() : 从napi_ref引用对象中取得napi_value
- napi_delete_reference() :删除napi_ref引用对象
- 通过
napi_create_reference()
方法将napi_value创建一个napi_ref,这个napi_ref是可以跨作用域传递的,然后在需要用到的地方用napi_get_reference_value()
方法将napi_ref还原为napi_value,用完后再用napi_delete_reference()
方法删除引用对象以便释放相关内存资源。
static napi_value addAsyncCallback(napi_env env, napi_callback_info info) {
size_t argc = 3;
napi_value args[3];
napi_value thisArg = nullptr;
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, &thisArg, nullptr));
...
auto addonData = new AddonData{
.asyncWork = nullptr,
};
NAPI_CALL(env, napi_get_value_double(env, args[0], &addonData->args[0]));
NAPI_CALL(env, napi_get_value_double(env, args[1], &addonData->args[1]));
NAPI_CALL(env, napi_create_reference(env, args[2], 1, &addonData->callback));
...
}
- NAPI_CALL()是用来调用NAPI中的API的。
主线程:创建异步工作项
创建异步工作项
在异步工作项工作时序图中位置,在图中用红框标记如下
- 第一步:在创建
异步工作项
前,分别声明addExecuteCB、addAsyncCompleteCB这2个函数,分别用作于napi_create_async_work(napi_env env,napi_value async_resource,napi_value async_resource_name,napi_async_execute_callback execute ,napi_async_complete_callback complete ,void* data,napi_async_work* result)函数的execute、complete参数。
- 第二步:利用NAPI框架提供的napi_create_async_work()函数创建
异步工作项
,将addExecuteCB、addAsyncCompleteCB这2个函数存入上下文数据的asyncWork属性
static void addExecuteCB(napi_env env, void *data) {
}
static void addAsyncCompleteCB(napi_env env, napi_status status, void *data) {
}
static napi_value addAsyncCallback(napi_env env, napi_callback_info info) {
...
napi_value resourceName = nullptr;
napi_create_string_utf8(env, "addCallback", NAPI_AUTO_LENGTH, &resourceName);
napi_create_async_work(env, nullptr, resourceName, addExecuteCB , addCallbackCompleteCB , (void *)addonData,&addonData->asyncWork);
...
}
主线程:异步工作项加入队列,等待调度
异步工作项加入队列,等待调度
在异步工作项工作时序图中位置,在图中用红框标记如下
static napi_value addAsyncCallback(napi_env env, napi_callback_info info) {
...
napi_queue_async_work(env, addonData->asyncWork);
...
}
主线程:原生方法返回临时返回值
- 调用napi_queue_async_work()将
异步工作项
加入调度队列,由异步work线程池统一调度,原生方法返回空值退出。
- 用户在调用接口的时候,接口实现将异步执行任务,任务执行结果以参数的形式提供给用户注册的回调函数(callback),这些参数的第一个是 Error 或 undefined 类型,分别表示执行出错与正常。
static napi_value addAsyncCallback(napi_env env, napi_callback_info info) {
...
napi_value result = 0;
NAPI_CALL(env, napi_get_null(env, &result));
return result;
}
work线程:执行业务逻辑、把计算结果写入上下文数据
执行业务逻辑
、把计算结果写入上下文数据
在异步工作项工作时序图中位置,在图中用红框标记如下
创建异步工作项前,声明了addExecuteCB这个函数,用作于napi_create_async_work()函数的execute参数。
- execute函数在异步工作项被调度后在work线程中执行
- 不阻塞主线程(不阻塞UI界面)
- 可执行IO、CPU密集型等任务。
执行业务逻辑
:业务逻辑计算是一个简单的加法,并把计算结果存入上下文数据
的result属性
把计算结果写入上下文数据
:把execute函数的结构体指向上下文数据
中结构体。
static void addExecuteCB(napi_env env, void *data) {
AddonData *addonData = (AddonData *)data;
addonData->result = addonData->args[0] + addonData->args[1];
}
EventLoop线程:把上下文中的结果转为JS类型、调用JS回调函数
把上下文中的结果转为JS类型
、调用JS回调函数
在异步工作项工作时序图中位置,在图中用红框标记如下
- 创建
异步工作项
前,声明addAsyncCompleteCB这个函数,用作于napi_create_async_work()函数的complete参数。
- 第一步:addAsyncCompleteCB从接收到的
上下文数据
中获取结果,调用napi_call_function()方法执行JS回调函数返回数据给JS。
- 第二步: 释放(删除)过程中创建的napi_ref引用对象、异步工作项等对象。
static void addCallbackCompleteCB(napi_env env, napi_status status, void *data) {
AddonData *addonData = (AddonData *)data;
napi_value callback = nullptr;
napi_get_reference_value(env, addonData->callback, &callback);
napi_value undefined = nullptr;
napi_get_undefined(env, &undefined);
napi_value result = nullptr;
napi_create_double(env, addonData->result, &result);
napi_value callbackResult = nullptr;
napi_call_function(env, undefined, callback, 1, &result, &callbackResult);
if (addonData->callback != nullptr) {
napi_delete_reference(env, addonData->callback);
}
napi_delete_async_work(env, addonData->asyncWork);
delete addonData;
}
- NAPI框架提供了
napi_call_function()
函数供扩展Natvie代码(C/C++代码)调用JS函数,用于执行回调函数等场景。函数定义如下:
NAPI_EXTERN napi_status napi_call_function(napi_env env,
napi_value recv,
napi_value func,
size_t argc,
const napi_value* argv,
napi_value* result)
参数说明:
[in] env: 传入接口调用者的环境,包含js引擎等,由框架提供,默认情况下直接传入即可。
[in] recv: 传给被调用的this对象。
[in] func: 被调用的函数.
[in] argc: 函数参数个数(对应函数数组的长度)。
[in] argv: 函数参数数组.
[out] result: func函数执行的返回值。 返回值:返回napi_ok表示转换成功,其他值失败。
- 因对象生命周期管理问题,
上下文数据
的callback属性的类型为napi_ref,需要调用napi_get_reference_value()
函数获取其指向的napi_value对象值才调用napi_call_function()
函数。 napi_get_reference_value函数定义:
NAPI_EXTERN napi_status napi_get_reference_value(napi_env env,
napi_ref ref,
napi_value* result)
参数说明:
[in] env: 传入接口调用者的环境,包含js引擎等,由框架提供,默认情况下直接传入即可。
[in] ref: napi_ref对象
[out] result: napi_ref引用的napi_value对象。 返回值:返回napi_ok表示转换成功,其他值失败。
- 执行回调函数是为了在异步操作之后调用JS函数
napi_delete_reference()
用于删除上下文数据
中定义的napi_ref对象callback。napi_ref引用对象在原生方法退出后不自动回收,由用户管理napi_ref类型对象的生命周期。
napi_delete_async_work()
用于删除异步工作线程,在异步调用的结尾释放async_work和相关业务数据的内存
Callback异步接口总结
以下图片为个人总结,可以在文末下载清晰的图片,下载之后推荐到[diagrams]。(https://app.diagrams.net/index.html)打开
Promise异步接口
Promise异步接口示例代码
hellonapi.cpp
#include <string.h>
#include<stdio.h>
#include "napi/native_node_api.h"
#include "napi/native_api.h"
struct AddonData {
napi_async_work asyncWork = nullptr;
napi_deferred deferred = nullptr;
napi_ref callback = nullptr;
double args[2] = {0};
double result = 0;
};
static void addExecuteCB(napi_env env, void *data) {
AddonData *addonData = (AddonData *)data;
addonData->result = addonData->args[0] + addonData->args[1];
}
static void addPromiseCompleteCB(napi_env env, napi_status status, void *data) {
AddonData *addonData = (AddonData *)data;
napi_value result = nullptr;
napi_create_double(env, addonData->result, &result);
napi_resolve_deferred(env, addonData->deferred, result);
if (addonData->callback != nullptr) {
napi_delete_reference(env, addonData->callback);
}
napi_delete_async_work(env, addonData->asyncWork);
delete addonData;
addonData = nullptr;
}
static napi_value addPromise(napi_env env, napi_callback_info info) {
size_t argc = 2;
napi_value args[2];
napi_value thisArg = nullptr;
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, &thisArg, nullptr));
napi_valuetype valuetype0;
NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0));
napi_valuetype valuetype1;
NAPI_CALL(env, napi_typeof(env, args[1], &valuetype1));
if (valuetype0 != napi_number || valuetype1 != napi_number) {
napi_throw_type_error(env, nullptr, "Wrong arguments. 2 numbers expected.");
return NULL;
}
napi_value promise = nullptr;
napi_deferred deferred = nullptr;
NAPI_CALL(env, napi_create_promise(env, &deferred, &promise));
auto addonData = new AddonData{
.asyncWork = nullptr,
.deferred = deferred,
};
NAPI_CALL(env, napi_get_value_double(env, args[0], &addonData->args[0]));
NAPI_CALL(env, napi_get_value_double(env, args[1], &addonData->args[1]));
napi_value resourceName = nullptr;
napi_create_string_utf8(env, "addAsyncCallback", NAPI_AUTO_LENGTH, &resourceName);
napi_create_async_work(env, nullptr, resourceName, addExecuteCB, addPromiseCompleteCB, (void *)addonData,
&addonData->asyncWork);
napi_queue_async_work(env, addonData->asyncWork);
return promise;
}
static napi_value registerFunc(napi_env env, napi_value exports)
{
static napi_property_descriptor desc[] = {
{ "addPromise", nullptr, addPromise, nullptr, nullptr, nullptr, napi_default, nullptr }
};
napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
return exports;
}
static napi_module hellonapiModule = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
.nm_register_func = registerFunc,
.nm_modname = "hellonapi",
.nm_priv = ((void*)0),
.reserved = { 0 },
};
extern "C" __attribute__((constructor)) void hellonapiModuleRegister()
{
napi_module_register(&hellonapiModule);
}
index.ets
import prompt from '@system.prompt';
import hellonapi from '@ohos.hellonapi'
@Entry
@Component
struct TestAdd {
build() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Button("hellonapi.addPromise(x, y).then(...)").margin(1).fontSize(20).onClick(() => {
let num1 = 123, num2 = 456
hellonapi.addPromise(num1, num2).then((result) => {
prompt.showToast({ message: `hellonapi.addPromise(${num1}, ${num2}) = ${result}` })
})
})
}
.width('100%')
.height('100%')
}
}
@ohos.hellonapi.d.ts
declare namespace hellonapi {
function addPromise(num1: number, num2: number): Promise<number>;
}
export default hellonapi;
创建Promise
- Promise整体处理流程和Callback方式一样,在此小节只讨论Promise不同于Callback的部分
- 首先创建Promise,NAPI框架中提供了napi_create_promise()函数用于创建Promise,调用该函数输出2个对象——deferred、promise。
- promise用于原生方法返回,deferred传入异步工作项的上下文数据。complete函数中,应用napi_resolve_deferred()函数 或 napi_reject_deferred() 函数返回数据。
函数定义如下:
napi_status napi_create_promise(napi_env env,
napi_deferred* deferred,
napi_value* promise);
参数说明:
[in] env: 传入接口调用者的环境,包含js引擎等,由框架提供,默认情况下直接传入即可。
[out] deferred: 返回接收刚创建的deferred对象,关联Promise对象,后面使用napi_resolve_deferred() 或 napi_reject_deferred() 返回数据。
[out] promise: 关联上面deferred对象的JS Promise对象 返回值:返回napi_ok表示转换成功,其他值失败。
static napi_value addPromise(napi_env env, napi_callback_info info) {
napi_value promise = nullptr;
napi_deferred deferred = nullptr;
NAPI_CALL(env, napi_create_promise(env, &deferred, &promise));
...
return promise;
}
初始化上下文数据
- 定义一个上下文数据结构,用于保存和传递数据。Promise方式加上deferred属性。
struct AddonData {
...
napi_deferred deferred = nullptr;
double args[2] = {0};
...
};
static napi_value addPromise(napi_env env, napi_callback_info info) {
size_t argc = 2;
napi_value args[2];
napi_value thisArg = nullptr;
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, &thisArg, nullptr));
...
napi_value promise = nullptr;
napi_deferred deferred = nullptr;
NAPI_CALL(env, napi_create_promise(env, &deferred, &promise));
...
NAPI_CALL(env, napi_get_value_double(env, args[0], &addonData->args[0]));
NAPI_CALL(env, napi_get_value_double(env, args[1], &addonData->args[1]));
...
}
- Callback方式在addCallback传入的是三个参数,Promise方式在addPromise传入的是两个参数。
创建异步工作项
- 同Callback方式一样在创建异步工作项前,分别声明2个函数,分别用作于napi_create_async_work()函数的execute、complete参数。
- 异步工作项创建OK后,将其存入上下文数据的asyncWork属性,并调用napi_queue_async_work()将异步工作项加入调度队列,由异步work线程池统一调度,原生方法返回Promise对象退出。
struct AddonData {
napi_async_work asyncWork = nullptr;
napi_deferred deferred = nullptr;
double args[2] = {0};
double result = 0;
};
static napi_value addPromise(napi_env env, napi_callback_info info) {
...
napi_value resourceName = nullptr;
napi_create_string_utf8(env, "addAsyncCallback", NAPI_AUTO_LENGTH, &resourceName);
napi_create_async_work(env, nullptr, resourceName, addExecuteCB, addPromiseCompleteCB, (void *)addonData,
&addonData->asyncWork);
napi_queue_async_work(env, addonData->asyncWork);
return promise;
}
execute 回调处理
此处完全同Callback方式,无需修改。
static void addExecuteCB(napi_env env, void *data) {
AddonData *addonData = (AddonData *)data;
addonData->result = addonData->args[0] + addonData->args[1];
}
complete 回调处理
- 调用NAPI提供的napi_resolve_deferred() 或 napi_reject_deferred() 返回数据。之后释放过程中创建的napi_ref引用对象、异步工作项等对象。
static void addPromiseCompleteCB(napi_env env, napi_status status, void *data) {
AddonData *addonData = (AddonData *)data;
napi_value result = nullptr;
napi_create_double(env, addonData->result, &result);
napi_resolve_deferred(env, addonData->deferred, result);
if (addonData->callback != nullptr) {
napi_delete_reference(env, addonData->callback);
}
napi_delete_async_work(env, addonData->asyncWork);
delete addonData;
addonData = nullptr;
}
规范异步接口
hellonapi.cpp
#include <string.h>
#include<stdio.h>
#include "napi/native_node_api.h"
#include "napi/native_api.h"
struct AddonData {
napi_async_work asyncWork = nullptr;
napi_deferred deferred = nullptr;
napi_ref callback = nullptr;
double args[2] = {0};
double result = 0;
};
static void addExecuteCB(napi_env env, void *data) {
AddonData *addonData = (AddonData *)data;
addonData->result = addonData->args[0] + addonData->args[1];
}
static void addCallbackCompleteCB(napi_env env, napi_status status, void *data) {
AddonData *addonData = (AddonData *)data;
napi_value callback = nullptr;
napi_get_reference_value(env, addonData->callback, &callback);
napi_value undefined = nullptr;
napi_get_undefined(env, &undefined);
napi_value result = nullptr;
napi_create_double(env, addonData->result, &result);
napi_value callbackResult = nullptr;
napi_call_function(env, undefined, callback, 1, &result, &callbackResult);
if (addonData->callback != nullptr) {
napi_delete_reference(env, addonData->callback);
}
napi_delete_async_work(env, addonData->asyncWork);
delete addonData;
}
static void addPromiseCompleteCB(napi_env env, napi_status status, void *data) {
AddonData *addonData = (AddonData *)data;
napi_value result = nullptr;
napi_create_double(env, addonData->result, &result);
napi_resolve_deferred(env, addonData->deferred, result);
if (addonData->callback != nullptr) {
napi_delete_reference(env, addonData->callback);
}
napi_delete_async_work(env, addonData->asyncWork);
delete addonData;
}
static napi_value addAsync(napi_env env, napi_callback_info info) {
size_t argc = 3;
napi_value args[3];
napi_value thisArg = nullptr;
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, &thisArg, nullptr));
napi_valuetype valuetype0;
NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0));
napi_valuetype valuetype1;
NAPI_CALL(env, napi_typeof(env, args[1], &valuetype1));
if (valuetype0 != napi_number || valuetype1 != napi_number) {
napi_throw_type_error(env, nullptr, "Wrong arguments. 2 numbers expected.");
return NULL;
}
auto addonData = new AddonData{
.asyncWork = nullptr,
};
if (argc == 2) {
napi_value promise = nullptr;
napi_deferred deferred = nullptr;
NAPI_CALL(env, napi_create_promise(env, &deferred, &promise));
addonData->deferred = deferred;
NAPI_CALL(env, napi_get_value_double(env, args[0], &addonData->args[0]));
NAPI_CALL(env, napi_get_value_double(env, args[1], &addonData->args[1]));
napi_value resourceName = nullptr;
napi_create_string_utf8(env, "addPromise", NAPI_AUTO_LENGTH, &resourceName);
napi_create_async_work(env, nullptr, resourceName, addExecuteCB, addPromiseCompleteCB, (void *)addonData,
&addonData->asyncWork);
napi_queue_async_work(env, addonData->asyncWork);
return promise;
} else {
napi_valuetype valuetype2;
NAPI_CALL(env, napi_typeof(env, args[2], &valuetype2));
if (valuetype2 != napi_function) {
napi_throw_type_error(env, nullptr, "Callback function expected.");
return NULL;
}
NAPI_CALL(env, napi_get_value_double(env, args[0], &addonData->args[0]));
NAPI_CALL(env, napi_get_value_double(env, args[1], &addonData->args[1]));
NAPI_CALL(env, napi_create_reference(env, args[2], 1, &addonData->callback));
napi_value resourceName = nullptr;
napi_create_string_utf8(env, "addCallback", NAPI_AUTO_LENGTH, &resourceName);
napi_create_async_work(env, nullptr, resourceName, addExecuteCB, addCallbackCompleteCB, (void *)addonData,
&addonData->asyncWork);
napi_queue_async_work(env, addonData->asyncWork);
napi_value result = 0;
NAPI_CALL(env, napi_get_null(env, &result));
return result;
}
}
static napi_value registerFunc(napi_env env, napi_value exports) {
static napi_property_descriptor desc[] = {
DECLARE_NAPI_FUNCTION("addAsync", addAsync),
};
NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc));
return exports;
}
static napi_module hellonapiModule = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
.nm_register_func = registerFunc,
.nm_modname = "hellonapi",
.nm_priv = ((void*)0),
.reserved = { 0 },
};
extern "C" __attribute__((constructor)) void hellonapiModuleRegister()
{
napi_module_register(&hellonapiModule);
}
index.ets
import prompt from '@system.prompt';
import hellonapi from '@ohos.hellonapi'
@Entry
@Component
struct TestAdd {
build() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Button("hellonapi.addAsync(x, y, callback)").margin(10).fontSize(20).onClick(() => {
let num1 = 123, num2 = 456
hellonapi.addAsync(num1, num2, (result) => {
prompt.showToast({ message: `hellonapi.addAsync(${num1}, ${num2}) = ${result}` })
})
})
Button("hellonapi.addAsync(x, y).then(...)").margin(10).fontSize(20).onClick(() => {
let num1 = 123, num2 = 456
hellonapi.addAsync(num1, num2).then((result) => {
prompt.showToast({ message: `hellonapi.addAsync(${num1}, ${num2}) = ${result}` })
})
})
}
.width('100%')
.height('100%')
}
}
@ohos.hellonapi.d.ts
declare namespace hellonapi {
function addAsync(num1: number, num2: number, callback:(result: number) => void): void;
function addAsync(num1: number, num2: number): Promise<number>;
}
export default hellonapi;
异步方法和同步方法.ts接口文件
同步方法
- 同步方法调用之后,将阻塞住JS线程直至获取到返回值。
- 命名:动词+Sync或动词+名词+Sync
- 格式:
- 无参:方法名()
- 有参:方法名Sync(必填参数[, 可选参数])
- 返回值:有
- 声明文件模板
declare namespace 模块名
{
function 方法名Sync(): 返回值类型;
function 方法名Sync(必填参数: 参数类型, options?: 可选参数类型): 返回值类型;
interface 可选参数类型 {
参数名: 参数类型;
}
}
export default 模块名;
declare namespace hellonapi {
function add(num1: number, num2: number): number;
}
export default hellonapi;
异步方法
- 异步方法调用整个过程不会阻碍调用者的工作。
- 命名:动词或动词+名词
- 格式:
- 无参:方法名([回调函数])
- 有参:方法名(必填参数[, 可选参数][, 回调函数])
- 返回值
- 若回调函数非空,则返回void
- 若回调函数为空,则返回Promise实例对象
- 声明文件模板
declare namespace 模块名 {
function 方法名(callback: AsyncCallback<结果数据类型>): void;
function 方法名(): Promise<结果数据类型>;
function 方法名(必填参数: 参数类型, callback: AsyncCallback<结果数据类型>): void;
function 方法名(必填参数: 参数类型, options: 可选参数类型, callback: AsyncCallback<结果数据类型>): void;
function 方法名(必填参数: 参数类型, options?: 可选参数类型): Promise<结果数据类型>;
interface 可选参数类型 {
参数名: 参数类型;
}
}
export default 模块名;
declare namespace hellonapi {
function addAsync(num1: number, num2: number, callback:(result: number) => void): void;
function addAsync(num1: number, num2: number): Promise<number>;
}
export default hellonapi;