三方库移植之NAPI开发--异步调用:Callback&Promise(四)

系统 OpenHarmony
本文共有三个示例,分别是Callback 异步接口示例、Promise 异步接口示例、规范异步接口示例。在本文末尾的资源中提供了这三个示例的源代码,读者可以下载在开发板上运行。

​想了解更多关于开源的内容,请访问:​

​51CTO 开源基础软件社区​

​https://ost.51cto.com​

三方库移植之NAPI开发系列文章《Hello OpenHarmony NAPI》、《C/C++与JS的数据类型转换》其接口都是同步的。对IO、CPU密集型任务需要异步处理。 NAPI支持异步模型,提供了Promise、Callback 2种方式。

  • 计算密集型程序适合C语言多线程,I/O密集型适合脚本语言开发的多线程。
  • CPU密集型也叫计算密集型,指的是系统的硬盘、内存性能相对CPU要好很多,此时,系统运作大部分的状况是CPU Loading 100%,CPU要读/写I/O(硬盘/内存),I/O在很短的时间就可以完成,而CPU还有许多运算要处理,CPU Loading很高。计算密集型任务的特点是要进行大量的计算,消耗CPU资源,比如计算圆周率、对视频进行高清解码等等,全靠CPU的运算能力。
  • IO密集型指的是系统的CPU性能相对硬盘、内存要好很多,此时,系统运作,大部分的状况是CPU在等I/O (硬盘/内存) 的读/写操作,此时CPU Loading并不高。

写在开头:

  • 本文在三方库移植之NAPI开发[1]—Hello OpenHarmony NAPI的基础上修改hellonapi.cpp、index.ets,接着学习NAPI异步模型的Promise、Callback方式。
  • 本文共有三个示例,分别是Callback 异步接口示例、Promise 异步接口示例、规范异步接口示例。在本文末尾的资源中提供了这三个示例的源代码,读者可以下载在开发板上运行。
  • 开发基于最新的OpenHarmony3.2Beta3版本及API9,标准系统开发板为润和软件DAYU200。

NAPI异步方式实现原理

  • 同步方式和异步方式
    同步方式,所有的代码处理都在原生方法(主线程)中完成。
    异步方式,所有的代码处理在多个线程中完成。

实现NAPI异步方法的步骤:

立即返回一个临时结果给js调用者

另起线程完成异步业务逻辑的执行

通过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:用于异步处理业务逻辑。因为不在JS线程中,所以不允许调用napi的接口。业务逻辑的返回值可以返回到complete回调中处理。
  • complete:可以调用napi的接口,将execute中的返回值封装成JS对象返回。此回调在JS线程中执行。

管理简单的异步操作的方法还有这些

  • NAPI_EXTERN napi_status napi_delete_async_work(napi_env env, napi_async_work work)
  • NAPI_EXTERN napi_status napi_queue_async_work(napi_env env, napi_async_work work)
  • NAPI_EXTERN napi_status napi_cancel_async_work(napi_env env, napi_async_work work)
  • 异步工作项中定义了2个函数
  • 一个函数用于执行工作项的业务逻辑,异步工作项被调度后,该函数从上下文数据中获取输入数据,在worker线程中完成业务逻辑计算(不阻塞主线程)并将结果写入上下文数据。
  • 业务逻辑处理函数执行完成或被取消后,触发EventLoop执行另一个函数,该函数从上下文数据中获取结果,转换为JS类型,调用JS回调函数或通过Promise resolve()返回结果。

NAPI支持异步模型

  • NAPI支持异步模型,提供了Promise、Callback方式。OpenHarmony标准系统异步接口实现支持Promise方式和Callback方式。
  • 标准系统异步接口实现规范要求,若引擎开启Promise特性支持,则异步方法必须同时支持Callback方式和Promise方式。
    1.由应用开发者决定使用哪种方式,通过是否传递Callback函数区分异步方法是Callback方式还是Promise方式。
    2.不传递Callback即为Promise方式(方法执行结果为Promise实例对象),否则为Callback方式,
  • ES6原生提供了Promise对象,Promise是异步编程的一种解决方案,可以替代传统的解决方案回调函数和事件;
    promise对象是一个异步操作的结果,提供了一些API使得异步执行可以按照同步的流表示出来,避免了层层嵌套的回调函数,保证了回调是以异步的方式进行调用的;
    用户在调用这些接口的时候,接口实现将异步执行任务,同时返回一个 Promise 对象,其代表异步操作的结果;
    在返回的结果的个数超过一个时,其以对象属性的形式返回。

ES6:全称ECMAScript 6.0。ECMAScript 是JavaScript语言的国际标准,JavaScript是ECMAScript的实现。

  • Promise 异步模型
  • ES6原生提供了Promise对象,Promise是异步编程的一种解决方案,可以替代传统的解决方案回调函数和事件;
    promise对象是一个异步操作的结果,提供了一些API使得异步执行可以按照同步的流表示出来,避免了层层嵌套的回调函数,保证了回调是以异步的方式进行调用的;
    用户在调用这些接口的时候,接口实现将异步执行任务,同时返回一个 Promise 对象,其代表异步操作的结果;
    在返回的结果的个数超过一个时,其以对象属性的形式返回。
  • Promise特点: 作为对象,Promise有两个特点:
    对象的状态不受外界影响;
    一旦状态改变了就不会再变,也就是说任何时候Promise都只有一种状态。
  • Callback 异步模型
    用户在调用这些接口的时候,接口实现将异步执行任务,任务执行结果以参数的形式提供给用户注册的回调函数,这些参数的第一个是 Error 或 undefined 类型,分别表示执行出错与正常。

Callback 异步接口

Callback 异步接口示例完成代码

hellonapi.cpp文件:

#include <string.h>
#include <stdio.h>
#include "napi/native_node_api.h"
#include "napi/native_api.h"
// 用户提供的上下文数据,在原生方法(初始化数据)、executeCB、completeCB之间传递数据
struct AddonData {
napi_async_work asyncWork = nullptr;
napi_deferred deferred = nullptr;
napi_ref callback = nullptr;
double args[2] = {0};
double result = 0;
};
// 业务逻辑处理函数,由worker线程池调度执行。
static void addExecuteCB(napi_env env, void *data) {
AddonData *addonData = (AddonData *)data;
// 执行复杂计算,不阻塞主线程。此处用一个加法简单示意。
addonData->result = addonData->args[0] + addonData->args[1];
}
// 业务逻辑处理完成回调函数,在业务逻辑处理函数执行完成或取消后触发,由EventLoop线程中执行。
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);
// 删除napi_ref对象
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) {
// 获取3个参数,值的类型是js类型(napi_value)
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));
// 获取并判断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;
}
// 异步工作项上下文用户数据,传递到异步工作项的execute、complete中传递数据
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));
// 创建async work,创建成功后通过最后一个参数接收async work的handle
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);
// 将刚创建的async work加到队列,由底层去调度执行
napi_queue_async_work(env, addonData->asyncWork);
// 原生方法返回空对象
napi_value result = 0;
NAPI_CALL(env, napi_get_null(env, &result));
return result;
}
// napi_addon_register_func
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;
}
// 定义napi_module,指定当前NAPI模块对应的模块名
//以及模块注册对外接口的处理函数,具体扩展的接口在该函数中声明
// nm_modname: 模块名称,对应eTS代码为import nm_modname from '@ohos.ohos_shared_library_name'
//示例对应eTS代码为:import hellonapi from '@ohos.hellonapi'
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 },
};
// 模块定义好后,调用NAPI提供的模块注册函数napi_module_register(napi_module* mod)函数注册到系统中。
// register module,设备启动时自动调用此constructor函数,把模块定义的模块注册到系统中
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;
/**
*
*
* @since 9
* @syscap SystemCapability.Ability.AbilityRuntime.AbilityCore
*/
}
export default hellonapi;

初始化上下文数据

  • 定义异步工作项上下文数据,根据业务需求自定义一个定义异步工作项上下文数据结构,用于在主线程方法、Work线程、EventLoop线程之间传递数据。
  • 本示例定义的上下文数据包含:异步工作项对象、回调函数、2个参数(加数、被加数)、业务逻辑处理结果等4个属性。
// 定义异步工作项上下文数据
// 用户提供的上下文数据,在原生方法(初始化数据)、executeCB、completeCB之间传递数据
struct AddonData {
napi_async_work asyncWork = nullptr; //异步工作对象
napi_ref callback = nullptr; //回调函数
double args[2] = {0}; //2个输入参数
double result = 0; //业务逻辑处理结果(返回值)
};

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_ref引用对象在原生方法退出后不自动回收,由用户管理此类型对象的生命周期。所以当前方法中,我们调用napi_create_reference()函数将接收到的napi_value类型的回调函数参数args[2]转换为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_create_reference() : 将napi_value包装成napi_ref引用对象
napi_get_reference_value() : 从napi_ref引用对象中取得napi_value
napi_delete_reference() :删除napi_ref引用对象
NAPI框架给出的解决方案是让开发者通过napi_value创建一个napi_ref,这个napi_ref是可以跨作用域传递的,然后在需要用到的地方再将napi_ref还原为napi_value,用完后再删除引用对象以便释放相关内存资源。
需要跨作用域传递napi_value时,往往需要用到上面这组方法把napi_value变成napi_ref。这是因为napi_value本质上只是一个指针,指向某种类型的napi数据对象。NAPI框架希望通过这种方式为开发者屏蔽各种napi数据对象的类型细节,类似于void* ptr的作用 。既然是指针,使用时就需要考虑它指向的对象的生命周期。

static napi_value addAsyncCallback(napi_env env, napi_callback_info info) {
// 获取3个参数,值的类型是js类型(napi_value)
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));
...
// 异步工作项上下文用户数据,传递到异步工作项的execute、complete中传递数据
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));
...
}

创建异步工作项

  • 第一步:在创建异步工作项前,分别声明addExecuteCB、addAsyncCompleteCB这2个函数,分别用作于napi_create_async_work(napi_env env,napi_value async_resource,napi_value async_resource_name,napi_async_execute_callbackexecute,napi_async_complete_callbackcomplete,void* data,napi_async_work* result)函数的execute、complete参数。
  • 第二步:利用NAPI框架提供的napi_create_async_work()函数创建异步工作项,将addExecuteCB、addAsyncCompleteCB这2个函数存入上下文数据的asyncWork属性
  • 第三步:调用napi_queue_async_work()将异步工作项加入调度队列,由异步work线程池统一调度,原生方法返回空值退出。
// 业务逻辑处理函数,由worker线程池调度执行。
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_CALL(env, napi_create_reference(env, args[2], 1, &addonData->callback));
// 创建异步工作项(async work),创建成功后通过最后一个参数接收异步工作项(async work)的handle
napi_value resourceName = nullptr;
napi_create_string_utf8(env, "addAsyncCallback", NAPI_AUTO_LENGTH, &resourceName);
napi_create_async_work(env, nullptr, resourceName, addExecuteCB, addAsyncCompleteCB, (void *)addonData, &addonData->asyncWork);
// 将刚创建的异步工作项(async work)加到队列,由work thread调度执行
napi_queue_async_work(env, addonData->asyncWork);

// 原生方法返回空对象
napi_value result = 0;
NAPI_CALL(env, napi_get_null(env, &result));
return result;
}

execute函数

创建异步工作项前,声明了addExecuteCB这个函数,用作于napi_create_async_work()函数的execute参数。

  • execute函数在异步工作项被调度后在work线程中执行。
  • 不阻塞主线程(不阻塞UI界面)。
  • 可执行IO、CPU密集型等任务。
  • 业务逻辑计算是一个简单的加法,并把计算结果存入上下文数据的result属性。
// 业务逻辑处理函数,由worker线程池调度执行。
static void addExecuteCB(napi_env env, void *data) {
AddonData *addonData = (AddonData *)data;

// 执行复杂计算,不阻塞主线程。此处用一个加法简单示意。
addonData->result = addonData->args[0] + addonData->args[1];
}

complete 函数

创建异步工作项前,声明addAsyncCompleteCB这个函数,用作于napi_create_async_work()函数的complete参数。

  • 第一步:addAsyncCompleteCB从接收到的上下文数据中获取结果,调用napi_call_function()方法执行JS回调函数返回数据给JS。
  • 第二步释放(删除)过程中创建的napi_ref引用对象、异步工作项等对象。
// 业务逻辑处理完成回调函数,在业务逻辑处理函数执行完成或取消后触发,由EventLoop线程中执行。
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);
// 删除napi_ref对象
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开发[4]异步调用:Callback&Promise-开源基础软件社区

// Methods to work with Functions
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开发[4]异步调用:Callback&Promise-开源基础软件社区

// Attempts to get a referenced value. If the reference is weak,
// the value might no longer be available, in that case the call
// is still successful but the result is nullptr.
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表示转换成功,其他值失败。

总结

#打卡不停更#三方库移植之NAPI开发[4]异步调用:Callback&Promise-开源基础软件社区

Promise异步接口

hellonapi.cpp:

#include <string.h>
#include<stdio.h>
#include "napi/native_node_api.h"
#include "napi/native_api.h"
// 用户提供的上下文数据,在原生方法(初始化数据)、executeCB、completeCB之间传递数据
struct AddonData {
napi_async_work asyncWork = nullptr;
napi_deferred deferred = nullptr;
napi_ref callback = nullptr;
double args[2] = {0};
double result = 0;
};
// 业务逻辑处理函数,由worker线程池调度执行。
static void addExecuteCB(napi_env env, void *data) {
AddonData *addonData = (AddonData *)data;
// 执行复杂计算,不阻塞主线程。此处用一个加法简单示意。
addonData->result = addonData->args[0] + addonData->args[1];
// addonData->result = addonData->args[0] + addonData[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);
// 删除napi_ref对象
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) {
// 获取2个参数,值的类型是js类型(napi_value)
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));
// 获取并判断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;
}
// 创建promise
napi_value promise = nullptr;
napi_deferred deferred = nullptr;
NAPI_CALL(env, napi_create_promise(env, &deferred, &promise));
// 异步工作项上下文用户数据,传递到异步工作项的execute、complete之间传递数据
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]));
// 创建async work,创建成功后通过最后一个参数(addonData->asyncWork)返回async work的handle
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);
// 将刚创建的async work加到队列,由底层去调度执行
napi_queue_async_work(env, addonData->asyncWork);
// 原生方法返回promise
return promise;
}
// napi_addon_register_func
//2.指定模块注册对外接口的处理函数,具体扩展的接口在该函数中声明
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;
}
// 1.先定义napi_module,指定当前NAPI模块对应的模块名
//以及模块注册对外接口的处理函数,具体扩展的接口在该函数中声明
// nm_modname: 模块名称,对应eTS代码为import nm_modname from '@ohos.ohos_shared_library_name'
//示例对应eTS代码为:import hellonapi from '@ohos.hellonapi'
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 },
};
//3.模块定义好后,调用NAPI提供的模块注册函数napi_module_register(napi_module* mod)函数注册到系统中。
// register module,设备启动时自动调用此constructor函数,把模块定义的模块注册到系统中
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>;
/**
*
*
* @since 9
* @syscap SystemCapability.Ability.AbilityRuntime.AbilityCore
*/
}
export default hellonapi;

规范异步接口

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;
};
// 业务逻辑处理函数,由worker线程池调度执行。
static void addExecuteCB(napi_env env, void *data) {
AddonData *addonData = (AddonData *)data;
// 执行复杂计算,不阻塞主线程。此处用一个加法简单示意。
addonData->result = addonData->args[0] + addonData->args[1];
}
// 业务逻辑处理完成回调函数,在业务逻辑处理函数执行完成或取消后触发,由EventLoop线程中执行。
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);
// 删除napi_ref对象
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);
// 删除napi_ref对象
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) {
// 获取3个参数,值的类型是js类型(napi_value)
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));
// 获取并判断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;
}
// 异步工作项上下文用户数据,传递到异步工作项的execute、complete中传递数据
auto addonData = new AddonData{
.asyncWork = nullptr,
};
if (argc == 2) {
// 创建promise
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]));
// 创建async work,创建成功后通过最后一个参数(addonData->asyncWork)返回async work的handle
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);
// 将刚创建的async work加到队列,由底层去调度执行
napi_queue_async_work(env, addonData->asyncWork);
// 返回promise
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));
// 创建async work,创建成功后通过最后一个参数接收async work的handle
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);
// 将刚创建的async work加到队列,由底层去调度执行
napi_queue_async_work(env, addonData->asyncWork);
// 原生方法返回空对象
napi_value result = 0;
NAPI_CALL(env, napi_get_null(env, &result));
return result;
}
}
// napi_addon_register_func
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;
}
// 1.先定义napi_module,指定当前NAPI模块对应的模块名
//以及模块注册对外接口的处理函数,具体扩展的接口在该函数中声明
// nm_modname: 模块名称,对应eTS代码为import nm_modname from '@ohos.ohos_shared_library_name'
//示例对应eTS代码为:import hellonapi from '@ohos.hellonapi'
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 },
};
//3.模块定义好后,调用NAPI提供的模块注册函数napi_module_register(napi_module* mod)函数注册到系统中。
// register module,设备启动时自动调用此constructor函数,把模块定义的模块注册到系统中
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>;
/**
*
*
* @since 9
* @syscap SystemCapability.Ability.AbilityRuntime.AbilityCore
*/
}
export default hellonapi;

#打卡不停更#三方库移植之NAPI开发[4]异步调用:Callback&Promise-开源基础软件社区


NAPI中的数据类型

  • NAPI使用的数据类型和Node.js N-API保持一致。OpenHarmony的NAPI(Native API)组件是一套对外接口基于Node.js N-API规范开发的原生模块扩展开发框架。
  • 通过查看foundation/arkui/napi/interfaces/inner_api/napi/native_node_api.h(编写NAPI拓展模块hellonapi.cpp需要包含的头文件)可以知道OpenHarmony基本的NAPI数据类型。

  • #include 中的js_native_api.h在ohos3.2beta3版本源码目录下路径为prebuilts/build-tools/common/nodejs/node-v12.18.4-linux-x64/include/node/js_native_api_types.h。

  • 然后再分析prebuilts/build-tools/common/nodejs/node-v12.18.4-linux-x64/include/node/js_native_api_types.h和third_party/node/src/js_native_api_types.h内容的差别。

#打卡不停更#三方库移植之NAPI开发[4]异步调用:Callback&Promise-开源基础软件社区

  • 两者内容一致,可以推测OpenHarmony中基本的NAPI数据类型和Node.js N-API中的保持一致。而接口名方面,napi提供的接口名与三方Node.js一致,目前支持部分接口,详情见​​libnapi.ndk.json​​文件
// JSVM API types are all opaque pointers for ABI stability
// typedef undefined structs instead of void* for compile time type safety
typedef struct napi_env__* napi_env;
typedef struct napi_value__* napi_value;
typedef struct napi_ref__* napi_ref;
typedef struct napi_handle_scope__* napi_handle_scope;
typedef struct napi_escapable_handle_scope__* napi_escapable_handle_scope;
typedef struct napi_callback_info__* napi_callback_info;
typedef struct napi_deferred__* napi_deferred;

预处理器发现 #include 指令后,就会寻找指令后面<>中的文件名,并把这个文件的内容包含到当前文件中。被包含文件中的文本将替换源代码文件中的#include 指令

  • 以typedef struct napi_env__* napi_env为例,搜遍Node.js的源码都找不到napi_value__定义,那这个定义是什么意思呢?c语言中,允许定义一个没有定义的结构体的指针。所以napi_value其实就是一个一级指针。他不需要类型信息。
  • 在callback回调方式的处理流程中,用到了这3个与napi_ref相关的方法:

napi_create_reference() : 将napi_value包装成napi_ref引用对象
napi_get_reference_value() : 从napi_ref引用对象中取得napi_value
napi_delete_reference() :删除napi_ref引用对象
当我们需要跨作用域传递napi_value时,往往需要用到上面这组方法把napi_value变成napi_ref。这是因为napi_value本质上只是一个指针,指向某种类型的napi数据对象。NAPI框架希望通过这种方式为开发者屏蔽各种napi数据对象的类型细节,类似于void* ptr的作用 。既然是指针,使用时就需要考虑它指向的对象的生命周期。

在我们的例子中,我们通过GetVisitCountAsync()方法的入参得到了js应用传递给C++的 callback function,存放在napi_value argv[1]中。但我们不能在complete_callback()方法中直接通过这个argv[1]去回调callback function(通过data对象传递也不行)。这时因为当代码执行到complete_callback()方法时,原先的主方法GetVisitCountAsync()早已执行结束, napi_value argv[1]指向的内存可能已经被释放另作他用了。

NAPI框架给出的解决方案是让开发者通过napi_value创建一个napi_ref,这个napi_ref是可以跨作用域传递的,然后在需要用到的地方再将napi_ref还原为napi_value,用完后再删除引用对象以便释放相关内存资源。

typedef作用就是定义类型别名

http://nodejs.cn/api/n-api.html。

prebuilts/build-tools/common/nodejs/node-v12.18.4-linux-x64/include/node/js_native_api_types.h:22:typedef struct napi_deferred__* napi_deferred;
third_party/node/src/js_native_api_types.h:22:typedef struct napi_deferred__* napi_deferred;
third_party/ejdb/src/bindings/ejdb2_node/js_native_api_types.h:22:typedef struct napi_deferred__* napi_deferred;

参考文章

https://ost.51cto.com/posts/14691。

关于NAPI标准库中导出的符号列表

  • NAPI它基于Node.js N-API规范开发,因此可参考Node.js N-API了解NAPI标准库中符号列表。本文以3.2beta3源码中的node三方库为例,从third_party/node/README.OpenSource中可得知3.2beta3移植的node版本为14.19.1,因此可参考的Node.js N-API链接为14.19.1版本,如下:https://nodejs.org/docs/latest-v14.x/api/n-api.html。
  • 标准库中导出的符号列表

符号类型

符号名

备注

FUNC

napi_module_register

FUNC

napi_get_last_error_info

FUNC

napi_throw

FUNC

napi_throw_error

FUNC

napi_throw_type_error

FUNC

napi_throw_range_error

FUNC

napi_is_error

FUNC

napi_create_error

FUNC

napi_create_type_error

FUNC

napi_create_range_error

FUNC

napi_get_and_clear_last_exception

FUNC

napi_is_exception_pending

FUNC

napi_fatal_error

FUNC

napi_open_handle_scope

FUNC

napi_close_handle_scope

FUNC

napi_open_escapable_handle_scope

FUNC

napi_close_escapable_handle_scope

FUNC

napi_escape_handle

FUNC

napi_create_reference

FUNC

napi_delete_reference

FUNC

napi_reference_ref

FUNC

napi_reference_unref

FUNC

napi_get_reference_value

FUNC

napi_create_array

FUNC

napi_create_array_with_length

FUNC

napi_create_arraybuffer

FUNC

napi_create_external

FUNC

napi_create_external_arraybuffer

FUNC

napi_create_object

FUNC

napi_create_symbol

FUNC

napi_create_typedarray

FUNC

napi_create_dataview

FUNC

napi_create_int32

FUNC

napi_create_uint32

FUNC

napi_create_int64

FUNC

napi_create_double

FUNC

napi_create_string_latin1

FUNC

napi_create_string_utf8

FUNC

napi_get_array_length

FUNC

napi_get_arraybuffer_info

FUNC

napi_get_prototype

FUNC

napi_get_typedarray_info

FUNC

napi_get_dataview_info

FUNC

napi_get_value_bool

FUNC

napi_get_value_double

FUNC

napi_get_value_external

FUNC

napi_get_value_int32

FUNC

napi_get_value_int64

FUNC

napi_get_value_string_latin1

FUNC

napi_get_value_string_utf8

FUNC

napi_get_value_uint32

FUNC

napi_get_boolean

FUNC

napi_get_global

FUNC

napi_get_null

FUNC

napi_get_undefined

FUNC

napi_coerce_to_bool

FUNC

napi_coerce_to_number

FUNC

napi_coerce_to_object

FUNC

napi_coerce_to_string

FUNC

napi_typeof

FUNC

napi_instanceof

FUNC

napi_is_array

FUNC

napi_is_arraybuffer

FUNC

napi_is_typedarray

FUNC

napi_is_dataview

FUNC

napi_is_date

FUNC

napi_strict_equals

FUNC

napi_get_property_names

FUNC

napi_set_property

FUNC

napi_get_property

FUNC

napi_has_property

FUNC

napi_delete_property

FUNC

napi_has_own_property

FUNC

napi_set_named_property

FUNC

napi_get_named_property

FUNC

napi_has_named_property

FUNC

napi_set_element

FUNC

napi_get_element

FUNC

napi_has_element

FUNC

napi_delete_element

FUNC

napi_define_properties

FUNC

napi_call_function

FUNC

napi_create_function

FUNC

napi_get_cb_info

FUNC

napi_get_new_target

FUNC

napi_new_instance

FUNC

napi_define_class

FUNC

napi_wrap

FUNC

napi_unwrap

FUNC

napi_remove_wrap

FUNC

napi_create_async_work

FUNC

napi_delete_async_work

FUNC

napi_queue_async_work

FUNC

napi_cancel_async_work

FUNC

napi_get_node_version

FUNC

napi_get_version

FUNC

napi_create_promise

FUNC

napi_resolve_deferred

FUNC

napi_reject_deferred

FUNC

napi_is_promise

FUNC

napi_run_script

FUNC

napi_get_uv_event_loop

Native API接口说明

符号类型

符号名

备注

FUNC

napi_run_script_path

运行JavaScript文件

#打卡不停更#三方库移植之NAPI开发[4]异步调用:Callback&Promise-开源基础软件社区

​想了解更多关于开源的内容,请访问:​

​51CTO 开源基础软件社区​

​https://ost.51cto.com​​。

责任编辑:jianghua 来源: 51CTO开源基础软件社区
相关推荐

2022-10-25 15:05:17

NAPI开发鸿蒙

2022-10-11 15:04:28

NAPI开发鸿蒙

2022-10-20 16:18:37

JS类型C/C++数据类型

2023-03-22 09:09:21

鸿蒙Speexdsp

2012-10-29 10:59:27

Windows 8

2009-10-20 16:48:30

C#委托

2009-12-21 14:10:26

WCF异步调用

2009-11-09 10:50:30

WCF异步调用

2009-08-21 13:18:25

C#异步调用

2023-02-07 15:43:13

三方库适配鸿蒙

2020-10-29 09:56:23

Linux静态库动态库

2011-07-25 14:14:49

iPhone SQLITE Pldatabase

2023-02-08 15:46:50

设备移植第三方内核适配

2009-07-01 13:58:00

JavaScript异

2009-11-06 15:54:15

WCF异步调用

2014-07-23 08:55:42

iOSFMDB

2019-07-30 11:35:54

AndroidRetrofit

2009-07-01 14:31:01

JavaScript异

2009-08-21 11:24:16

C#异步调用

2009-07-01 14:37:14

JavaScript异
点赞
收藏

51CTO技术栈公众号