乐趣区

关于javascript:Promise-In-Nodejs-C-Addon

Normally, when writing an Add-on, we could code like this:

// Somewhere
void doSomething(std::string& str) {str = "Hello, world";}

/// The implementation of add-on
static void fork(const FunctionCallbackInfo<Value>& args) {Isolate* iso = args.GetIsolate();
    /// Do something
    doSomething(str);
    /// Callback
    Local<Value> val = args[3]; // Assuming the forth arguments is the callback function.
    if (val->IsFunction()) {Local<Function> func = Function::Cast(val);
        func->Call(iso->GetCurrentContext()->Global(), Integer::New(iso, 0x1234));
    }
} // end of fork

The above code works fine unless Promise involved. The notorious segment fault drives me mad. The reason is that the Promise.resolve will probably be called back from a different thread. Finally I found a way out:

/// Turn our processing into async
// Define the prototype of callback.
using callback = boost::function<void(int, const std::string&)>;
typedef struct {
    std::string value;
    callback cb;
} async_struc;

AsyncWorker<async_struc> async_worker;

void doSomething(std::string& arg, const callback& cb {async_struc* data = new async_struc();
    
    data.value = arg;
    data.cb = cb;
    
    async_worker.post(data, [&](async_struc* x) {
        // Do the work.
        x->value += "Hello, world!";
    }, [&](async_struc* x, bool canceled) {x->cb(canceled ? -1 : 0, x->value);
    });
}

When crossing thread boundary, JavaScript object should be wrapped with Persistent. Howover, V8 forbids us using explicit ctor of Persistent. An alternative is to use a structure to hold the Persistent wrapped object and pass a structure pointer to make lambda expression capture it.
The structure looks like :

typedef struct {
    std::string value; //<! The value came from arguments.
    Persistent<Function> cb;
} js_callback_info_t;

Also, according to V8 reference, we should get current Isolate object under different threads. A HandleScope is needed too.

Isolate* asyncIso = Isolate::GetCurrent();
HandleScope scope(asyncIso);

Now rewrite the js add-on method:

static void fork(const FunctionCallbackInfo<Value>& args) {Isolate* iso = args.GetIsolate();
    // Get string argument
    String::Utf8Value utf8str(args[0]);
    std::string str(*utf8str);

    /// Save the callback information
    Local<Value> val = args[3]; // Assuming the forth arguments is the callback function.
    js_callback_info_t* cb_info = new js_callback_info();
    
    cb_info->value = str;
    cb_info->cb.Reset(iso, Local<Function>::Cast(val);
    
    doSomething(str, [&, cb_info](int err, const std::string& res) mutable {Isolate* asyncIso = Isolate::GetCurrent();
        HandleScope scope(asyncIso);
        Local<Function> func = Local<Function>::New(asyncIso, cb_info->cb);
        Local<Value> params[] = { Integer::New(asyncIso, err), String::NewUtf8(asyncIso, res.c_str()) };
        func->Call(asyncIso->GetCurrentContext()->Global(), 2, params);
        // Dispose persistent object
        func->cb.Reset();
        // Release the memory allocated before
        delete cb_info;
    });
} // end of fork

Now the Add-on is Promise-ready.

Good luck!

退出移动版