Openharmony应用NAPI详解--进阶篇1
NAPI面向C++的异步接口
3.C++实现NAPI异步接口需要做到三步
-
同步返回结果给js/ets调用者
-
另起线程完成异步操作
-
通过回调(callback)或Promise将异步操作结果返回
4.异步接口
// foundation/filemanagement/dfs_service/frameworks/js/napi/src/sendfile_napi.cpp... DECLARE_NAPI_FUNCTION("sendFile", JsSendFile),...
根据映射,同步接口sendFile对应C++的实现是JsSendFile。
-
函数声明
// foundation/filemanagement/dfs_service/frameworks/js/napi/src/sendfile_napi.cppnapi_value JsSendFile(napi_env env, napi_callback_info info){......}
-
获取入参
同样是获取参数个数与参数,根据d.ts里sendFile接口定义,第五个参数为callback或没有参数,callback接口返回一个void, promise接口返回一个promise对象。
// foundation/filemanagement/dfs_servicedeclare namespace SendFile { function sendFile(deviceId: string, sourPath: Array, destPath: Array, fileCount: number, callback: AsyncCallback); function sendFile(deviceId: string, sourPath: Array, destPath: Array, fileCount: number): Promise;}
此时获取参数个数与参数类型与具体数据,被一个类NFuncArg对象funcArg进行解析。
// foundation/filemanagement/dfs_service/frameworks/js/napi/src/sendfile_napi.cppnapi_value JsSendFile(napi_env env, napi_callback_info info){NFuncArg funcArg(env, info); if (!funcArg.InitArgs(DFS_ARG_CNT::FOUR, DFS_ARG_CNT::FIVE)) { NError(EINVAL).ThrowErr(env, "Number of arguments unmatched"); return nullptr; }...}
我们熟悉的获取参数的方法napi_get_cb_info在类NFuncArg中可以找到。
bool NFuncArg::InitArgs(std::function argcChecker){ SetArgc(0); argv_.reset(); size_t argc; napi_value thisVar; napi_status status = napi_get_cb_info(env_, info_, &argc, nullptr, &thisVar, nullptr); if (status != napi_ok) { HILOGE("Cannot get num of func args for %{public}d", status); return false; } if (argc) { argv_ = make_unique(argc); status = napi_get_cb_info(env_, info_, &argc, argv_.get(), &thisVar, nullptr); if (status != napi_ok) { HILOGE("Cannot get func args for %{public}d", status); return false; } } SetArgc(argc); SetThisVar(thisVar); return argcChecker();}
略过参数的类型转化与参数值校验,直接进入callback与promise的判断。
napi_value JsSendFile(napi_env env, napi_callback_info info){...if (funcArg.GetArgc() == DFS_ARG_CNT::FOUR) { return NAsyncWorkPromise(env, thisVar).Schedule(procedureName, cbExec, cbComplete).val_; } else if (funcArg.GetArgc() == DFS_ARG_CNT::FIVE) { NVal cb(env, funcArg[DFS_ARG_POS::FIFTH]);// cb为传入的回调函数 return NAsyncWorkCallback(env, thisVar, cb).Schedule(procedureName, cbExec, cbComplete).val_; }}
此时的判断的方式通过传入的参数个数是4还是5来判断callback与promise。
如果有参数个数相同的情况时,可以根据参数的类型来进行判断。
// 可以改写成如下判断napi_value JsSendFile(napi_env env, napi_callback_info info){...napi_valuetype valueType = napi_undefined; // (此处假设promise接口也有第5个参数,否则会报异常错误) napi_typeof(env, funcArg[DFS_ARG_POS::FIFTH], &valueType); if (valueType == napi_function) { // Js调用的是callback接口 ... } else {//Js调用的是promise接口 ... }...}
此时,异步的方式需要另起线程,将参数传入线程后,JsSendFile直接返回。
NAPI框架中,异步的线程实现基于napi_create_async_work函数创建异步工作项。
代码中通过两个封装类NAsyncWorkPromise/NAsyncWorkCallback的Schedule来实现。可以在两个类的Schedule接口找到napi_create_async_work方法调用。
// foundation/filemanagement/file_api/interfaces/kits/js/src/common/napi/n_async/n_async_work_callback.cppNVal NAsyncWorkCallback::Schedule(string procedureName, NContextCBExec cbExec, NContextCBComplete cbComplete){... napi_status status = napi_create_async_work(env_, nullptr, resource, CallbackExecute, CallbackComplete, ctx_, &ctx_->awork_); if (status != napi_ok) { HILOGE("INNER BUG. Failed to create async work for %{public}d", status); return NVal(); }...}// foundation/filemanagement/file_api/interfaces/kits/js/src/common/napi/n_async/n_async_work_promise.cppNVal NAsyncWorkPromise::Schedule(string procedureName, NContextCBExec cbExec, NContextCBComplete cbComplete){... napi_value resource = NVal::CreateUTF8String(env_, procedureName).val_; status = napi_create_async_work(env_, nullptr, resource, PromiseOnExec, PromiseOnComplete, ctx_, &ctx_->awork_); if (status != napi_ok) { HILOGE("INNER BUG. Failed to create async work for %{public}d", status); return NVal(); }...}
首先先分析下napi_create_async_work的方法定义
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线程中执行,用于处理业务逻辑,并得到结果。
[in] complete: execute参数指定的函数执行完成或取消后,触发执行该函数,将结果返回给JS。此函数在EventLoop中执行。
[in] data: 异步工作项上下文数据(Addon),用于在主线程接口实现方法、execute、complete之间传递数据。
[out] result: napi_async_work*指针,返回创建的异步工作项对象。
返回值:返回napi_ok表示转换成功,其他值失败
异步线程的处理由execute和complete两个自定义函数实现。data即是execute和complete两个自定义函数的主要传入参数。data的结构可以自定义结构。
// data类型定义class NAsyncContext {public: UniError err_; NVal res_; NContextCBExec cbExec_; NContextCBComplete cbComplete_; napi_async_work awork_; NRef thisPtr_; explicit NAsyncContext(NVal thisPtr) : err_(0), res_(NVal()), cbExec_(nullptr), cbComplete_(nullptr), awork_(nullptr), thisPtr_(thisPtr) {} virtual ~NAsyncContext() = default;};
callback方式的处理方式
执行napi_create_async_work异步线程后,通过napi_queue_async_work将创建的async work添加到队列,由NAPI框架的底层去调度后执行。
// foundation/filemanagement/file_api/interfaces/kits/js/src/common/napi/n_async/n_async_work_callback.cppNVal NAsyncWorkCallback::Schedule(string procedureName, NContextCBExec cbExec, NContextCBComplete cbComplete){ if (!ctx_->cb_ || !ctx_->cb_.Deref(env_).TypeIs(napi_function)) { UniError(EINVAL).ThrowErr(env_, "The callback shall be a funciton"); return NVal(); }// data内成员赋值 ctx_->cbExec_ = move(cbExec); ctx_->cbComplete_ = move(cbComplete); napi_value resource = NVal::CreateUTF8String(env_, procedureName).val_; napi_status status = napi_create_async_work(env_, nullptr, resource, CallbackExecute, CallbackComplete, ctx_, &ctx_->awork_); if (status != napi_ok) { HILOGE("INNER BUG. Failed to create async work for %{public}d", status); return NVal(); } status = napi_queue_async_work(env_, ctx_->awork_);// 创建的async work添加到队列 if (status != napi_ok) { HILOGE("INNER BUG. Failed to queue async work for %{public}d", status); return NVal(); } ctx_ = nullptr; // The ownership of ctx_ has been transfered return NVal::CreateUndefined(env_);}
实现自定义的execute和complete,分别对应CallbackExecute, CallbackComplete。处理完成后将
// foundation/filemanagement/file_api/interfaces/kits/js/src/common/napi/n_async/n_async_work_callback.cppstatic void CallbackExecute(napi_env env, void *data){...}static void CallbackComplete(napi_env env, napi_status status, void *data){... //callback为js/ets传入的回调函数 napi_value callback = ctx->cb_.Deref(env).val_;... napi_status stat = napi_call_function(env, global, callback, argv.size(), argv.data(), &tmp); if (stat != napi_ok) { HILOGE("Failed to call function for %{public}d", stat); } napi_close_handle_scope(env, scope); napi_delete_async_work(env, ctx->awork_); delete ctx;}
此处的ctx->cb_成员赋值,由NVal与NAsyncWorkCallback的两个构造函数完成
// foundation/filemanagement/dfs_service/frameworks/js/napi/src/sendfile_napi.cppnapi_value JsSendFile(napi_env env, napi_callback_info info){... if (funcArg.GetArgc() == DFS_ARG_CNT::FOUR) { return NAsyncWorkPromise(env, thisVar).Schedule(procedureName, cbExec, cbComplete).val_; } else if (funcArg.GetArgc() == DFS_ARG_CNT::FIVE) { NVal cb(env, funcArg[DFS_ARG_POS::FIFTH]); return NAsyncWorkCallback(env, thisVar, cb).Schedule(procedureName, cbExec, cbComplete).val_; }...}
至此NAPI的异步回调流程完成。
后续更精彩
1.Openharmony应用NAPI详解--进阶篇2