如何 return 通过异步回调在节点本机代码中创建新对象?

How to return a new object created in node native code via asynchronous callback?

我正在尝试创建一个执行以下操作的节点插件:

// js
addon.makeObject((err, obj) => {
    if (err) { /* handle error */ }
    console.log('New object ID=%d', obj.getID());
      :
});

makeObject() 有点像 "asynchronous object factory",其中在后台(使用 Nan::AsyncWorker)的本机 C++ 代码中创建一个新实例,并进行必要的设置,然后返回给 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?我一直在谷歌上寻找方法,但到目前为止没有运气......

我自己找到了答案。关键是在构造函数上使用 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)