Как вернуть новый объект, созданный в собственном коде узла через асинхронный обратный вызов?

Я пытаюсь создать аддон узла, который делает что-то вроде этого:

// 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)
Другие вопросы по тегам