为了与 Node.js 的 事件循环 机制一致,如果原生模块中涉及到耗时操作,它也不应该阻塞事件循环主线程(即执行 JavaScript 代码的线程)的执行。这意味着原生模块需要借助于 libuv 的部分功能实现自己的 异步(Asynchronous),这样就不必等到耗时的原生函数执行完成才返回结果。
node-addon-api 提供了AsyncWorker
抽象类实现异步操作,这基本上可以覆盖掉大部分需要异步的场景。该类通过对数据在事件循环线程和工作线程之间移动的抽象,来帮助开发者管理异步操作。
如果AsyncWorker
不能解决问题,node-addon-api 还提供了AsyncContext
和CallbakScope
来确保异步操作能够被运行环境很好的追踪。
class AsyncWorker
这是一个抽象类,开发者应该继承这个类,并至少实现其Execute()
方法。
class EchoWorker : public AsyncWorker {
public:
EchoWorker(Function& callback, std::string& echo)
: AsyncWorker(callback), echo(echo) {}
~EchoWorker() {}
void Execute() override {
/* cpu heavy task */
std::this_thread::sleep_for(std::chrono::seconds(3));
}
void OnOK() override {
HandleScope scope(Env());
Callback().Call({
Env().Null(),
String::New(Env(), echo)
});
}
private:
std::string echo;
}
实例化一个自定义的 EchoWorker 对象需要两个参数:
Function&
回调函数,在任务函数执行成功之后由事件循环主线程执行- 一个或者多个自定义数据
其中回调函数传递给父类 AsyncWorker 的构造函数,AsyncWorker 的构造函数有:
explicit AsyncWorker(const Function& callback);
explicit AsyncWorker(const Function& callback, const char* resource_name);
explicit AsyncWorker(const Function& callback, const char* resource_name, const Object& resource);
explicit AsyncWorker(const Object& receiver, const Function& callback);
explicit AsyncWorker(const Object& receiver, const Function& callback, const char* resource_name);
explicit AsyncWorker(const Object& receiver, const Function& callback, const char* resource_name, const Object& resource);
explicit AsyncWorker(Env env);
explicit AsyncWorker(Env env, const char* resource_name);
explicit AsyncWorker(Env env, const char* resource_name, const Object& resource);
callback
: 异步任务完成之后由 JavaScript 主线程执行的回调函数resource_name
: async_hooks API 暴露的用于诊断的资源类型标识符(不知道具体干嘛的)resource
: 与异步操作相关、需要被传递给 async_hooks 的对象receiver
: 传递给被调函数的 this 对象(谁的 this?)
构造 AsyncWorker 的一个关键参数是回调函数,该函数将在异步任务执行完成之后被放入任务队列中,在事件循环主线程上运行。
扩展 AsyncWorker 类需要复写其 Execute 方法,该方法是实例化子类时自动执行的任务,一般是会阻塞主线程的任务,所以才需要异步执行。
getters
Env()
Receiver()
Callback()
FunctionReference& AsyncWorker::Callback();
创建异步任务时的回调函数的函数对象的强引用。
FunctionReference 对象与 Function 对象类似,通过执行其Call()
方法可以执行回调函数。
AsyncWorker 会将异步任务(Execute 方法)的返回结果作为参数传递给回调函数。
所以执行回调函数的代码看起来像:
Callback().Call(<异步任务的返回结果>)
actions
Queue()
开始排队执行任务。
Cancel()
如果排队的任务尚未开始执行,取消它。已经开始运行的任务不能被取消。如果取消成功,OnOK 和 OnError 方法都不会被调用。
SetError()
void AsyncWorker::SetError(string& msg);
在执行异步任务的过程中随时可以通过此方法将任务状态标记为 失败。
一旦调用此方法,异步任务将停止执行,并调用 OnError 方法。
SuppressDestruct()
防止异步任务完成、OnOK 方法被调用后解构 AsyncWorker 对象。
Destroy()
如果没有调用 SuppressDestruct 方法,在 OnOK 或者 OnError 方法被调用后会立即调用此方法解构实例。
默认使用 delete 删除实例,可以保证通过除了 new 之外的方法构建的实例也能被正确删除。
overrides
Execute()
virtual void AsyncWorker::Execute() = 0;
使用 libuv 提供的 工作线程 在 事件循环线程 外执行一些耗时任务。
扩展 AsyncWorker 的子类必须实现这一方法,这个方法是异步任务的任务主体。
因为该任务是在主线程之外执行,所以不能使用任何 node-addon-api 提供的 API,不能执行任何可能调用 JavaScript 的代码。
所有需要调用 node-addon-api 的 API 和执行 JavaScript 的代码都应该放到 OnOK 和 OnError 方法中。
OnOK 和 OnError 方法在异步任务完成之后,由主线程执行。
GetResult()
virtual vector<napi_value> AsyncWorker::GetResult(Env env);
此方法用来定义执行回调函数时需要传入的参数,回调函数默认以无参方法执行,有参执行时需要复写此方法。
OnOK()
virtual void AsyncWorker::OnOK();
OnOK 方法在 Execute 方法正常执行完之后由主线程执行,所以可以使用 node-addon-api 和调用 JavaScript 的代码。
AsyncWorker 提供默认实现:执行不带参数的回调函数,需要带参数可以直接复写此方法或者复写 GetResult 方法。
OnError()
virtual void AsyncWorker::OnError(Error& error);
OnError 方法在 Execute 方法执行出错之后立即由主线程执行,所以也可以使用 node-addon-api 和调用 JavaScript 的代码。
AsyncWorker 提供默认实现:执行回调函数,将错误对象作为第一个参数传递给回调函数。
以出错的方法结束 Execute 方法的方式有两种:
- 启用 C++ 异常时,使用 throw 抛出一个异常
- 使用 SetError 设置错误
OnWorkComplete()
virtual void AsyncWorker::OnWorkComplete(Env env, napi_status status);
在 JavaScript 工作线程执行完任务之后,此方法会检测任务的执行状态,根据任务状态调用 OnOK 或者 OnError 并传递任务结果。
如果任务被成功取消,此方法不会被调用,所以 OnOK 和 OnError 方法也不会被调用。
当结果被传递出去(开始执行 OnOK 或者 OnError)之后,如果没有调用 SuppressDestruct 方法,AsyncWorker 对象将会被解构。
OnExecute()
virtual void OnExecute(Env env);
这个方法在异步任务被分配到工作线程时立即被调用,它的作用就是调用 Execute 方法开始在工作线程中执行任务。
此方法虽然接受一个 Env 对象作为参数,但是在其内部不能使用任何 node-addon-api 和包含调用 JavaScript 的代码,因为它是在工作线程中执行的。
how to use
执行 addon 中的一个异步方法,与执行普通的异步函数类似。例如
setTimeout(() => { /* ... */ }, 200)
我们会传递一个 回调函数 给原生方法,也可以传递其他参数给原生方法,假设我们传递了一个额外的字符串:
exampleAddon.echo("hello c++", v => console.log(v))
在 C++ 中我们定义了一个 Echo 函数,并将其导出为 echo:
#include <napi.h>
using namespace std;
using namespace Napi;
void Echo(const CallbackInfo& info) {
string msg = info[0].as<String>();
Function cb = info[1].as<Function>();
EchoWorker* wk = new EchoWorker(cb, msg);
wk->Queue();
}
实例化一个对象,调用其 Queue 方法即可。
评论区