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.