Как вернуть новый объект, созданный в собственном коде узла через асинхронный обратный вызов?
Я пытаюсь создать аддон узла, который делает что-то вроде этого:
// js
addon.makeObject((err, obj) => {
if (err) { /* handle error */ }
console.log('New object ID=%d', obj.getID());
:
});
makeObject()
это своего рода "фабрика асинхронных объектов", в которой новый экземпляр создается внутри собственного кода C++ в фоновом режиме (с использованием Nan::AsyncWorker) с необходимыми настройками, а затем возвращается к земле NodeJS посредством обратного вызова.
Вот фрагмент нативного кода:
// cpp
#include <nan.h>
#ifndef _WIN32
#include <unistd.h>
#define Sleep(x) usleep((x)*1000)
#endif
class MyClass : public Nan::ObjectWrap {
public:
class Worker : public Nan::AsyncWorker {
public:
friend class MyClass;
explicit Worker(Nan::Callback* callback) : Nan::AsyncWorker(callback) {}
private:
virtual void Execute() {/* some long running task */ Sleep(1000); }
virtual void HandleOKCallback() {
MyClass *mc = new MyClass();
v8::Local<v8::Object> obj = Nan::New<v8::Object>();
mc->Wrap(obj); // ==> Assertion failed: (object->InternalFieldCount() > 0)
v8::Local<v8::Value> argv[] = { Nan::Undefined(), obj };
callback->Call(2, argv);
}
};
explicit MyClass() : _id(idCtr++) {}
~MyClass() {}
static void Init(v8::Local<v8::Object> exports) {
v8::Local<v8::FunctionTemplate> tpl = Nan::New<v8::FunctionTemplate>(New);
tpl->SetClassName(Nan::New("MyClass").ToLocalChecked());
tpl->InstanceTemplate()->SetInternalFieldCount(1);
Nan::SetPrototypeMethod(tpl, "getID", GetID);
constructor.Reset(tpl->GetFunction());
exports->Set(Nan::New("MyClass").ToLocalChecked(), Nan::GetFunction(tpl).ToLocalChecked());
}
private:
int _id;
static Nan::Persistent<v8::Function> constructor;
static NAN_METHOD(New) {
if (info.IsConstructCall()) {
MyClass* mc = new MyClass();
mc->Wrap(info.This());
info.GetReturnValue().Set(info.This());
} else {
const int argc = 0;
v8::Local<v8::Value> argv[argc] = {};
v8::Local<v8::Function> cons = Nan::New<v8::Function>(constructor);
info.GetReturnValue().Set(Nan::NewInstance(cons, argc, argv).ToLocalChecked());
}
}
static NAN_METHOD(GetID) {
MyClass *mc = ObjectWrap::Unwrap<MyClass>(info.Holder());
info.GetReturnValue().Set(Nan::New<v8::Integer>(mc->_id));
}
static int idCtr;
};
Nan::Persistent<v8::Function> MyClass::constructor;
int MyClass::idCtr = 0;
NAN_METHOD(MakeObject) {
// Check arguments here...
Nan::Callback *cb = new Nan::Callback(info[0].As<v8::Function>());
Nan::AsyncQueueWorker(new MyClass::Worker(cb)); // starts the worker
info.GetReturnValue().Set(Nan::Undefined());
}
void InitAll(v8::Local<v8::Object> exports) {
exports->Set( Nan::New("makeObject").ToLocalChecked(),
Nan::New<v8::FunctionTemplate>(MakeObject)->GetFunction());
MyClass::Init(exports);
}
NODE_MODULE(NODE_GYP_MODULE_NAME, InitAll)
Хотя это компилируется, это терпит неудачу в этой строке:
mc->Wrap(obj);
Это ошибка:
Assertion failed: (object->InternalFieldCount() > 0), function Wrap, file ../node_modules/nan/nan_object_wrap.h, line 55.
Что такое InternalFieldCount
? Я гуглял, чтобы найти способ, но безуспешно до сих пор...
1 ответ
Решение
Я нашел ответ сам. Ключ должен был использовать Nan::NewInstance в конструкторе - так же, как обрабатывать JS-код, вызывающий конструктор, как обычную функцию.
Вот полный код, который работает:
// addon.cpp
#include <nan.h>
#ifndef _WIN32
#include <unistd.h>
#define Sleep(x) usleep((x)*1000)
#endif
class MyClass : public Nan::ObjectWrap {
public:
class Worker : public Nan::AsyncWorker {
public:
friend class MyClass;
explicit Worker(Nan::Callback* callback) : Nan::AsyncWorker(callback) {}
private:
virtual void Execute() {/* some long running task */ Sleep(1000); }
virtual void HandleOKCallback() {
///////////////////////////////////////////////////////////
// Create MyClass instance and pass it to JS via callback
const int argc = 0;
v8::Local<v8::Value> argv[argc] = {};
v8::Local<v8::Function> cons = Nan::New<v8::Function>(constructor);
v8::Local<v8::Object> obj = Nan::NewInstance(cons, argc, argv).ToLocalChecked();
v8::Local<v8::Value> _argv[] = { Nan::Undefined(), obj };
callback->Call(2, _argv);
}
};
explicit MyClass() : _id(idCtr++) {}
~MyClass() {}
static NAN_MODULE_INIT(Init) {
v8::Local<v8::FunctionTemplate> tpl = Nan::New<v8::FunctionTemplate>(New);
tpl->SetClassName(Nan::New("MyClass").ToLocalChecked());
tpl->InstanceTemplate()->SetInternalFieldCount(1);
Nan::SetPrototypeMethod(tpl, "getID", GetID);
constructor.Reset(tpl->GetFunction());
target->Set(Nan::New("MyClass").ToLocalChecked(), Nan::GetFunction(tpl).ToLocalChecked());
}
private:
int _id;
static Nan::Persistent<v8::Function> constructor;
static NAN_METHOD(New) {
if (info.IsConstructCall()) {
MyClass* mc = new MyClass();
mc->Wrap(info.This());
info.GetReturnValue().Set(info.This());
} else {
const int argc = 0;
v8::Local<v8::Value> argv[argc] = {};
v8::Local<v8::Function> cons = Nan::New<v8::Function>(constructor);
info.GetReturnValue().Set(Nan::NewInstance(cons, argc, argv).ToLocalChecked());
}
}
static NAN_METHOD(GetID) {
MyClass *mc = ObjectWrap::Unwrap<MyClass>(info.Holder());
info.GetReturnValue().Set(Nan::New<v8::Integer>(mc->_id));
}
static int idCtr;
};
Nan::Persistent<v8::Function> MyClass::constructor;
int MyClass::idCtr = 0;
NAN_METHOD(MakeObject) {
// Check arguments here...
Nan::Callback *cb = new Nan::Callback(info[0].As<v8::Function>());
Nan::AsyncQueueWorker(new MyClass::Worker(cb)); // starts the worker
info.GetReturnValue().Set(Nan::Undefined());
}
NAN_MODULE_INIT(InitAll) {
target->Set( Nan::New("makeObject").ToLocalChecked(),
Nan::New<v8::FunctionTemplate>(MakeObject)->GetFunction());
MyClass::Init(target);
}
NODE_MODULE(NODE_GYP_MODULE_NAME, InitAll)