Skip to content

Commit ebcb9fe

Browse files
authored
example: add example for calling JS from AsyncWorker::Execute() (#211)
This was asked for in nodejs/node-addon-api#110 (comment) and nodejs/node-addon-api#110 (comment). The solution was described in text in nodejs/node-addon-api#110 (comment) but it would be better to have a code example that folks can refer to. Signed-off-by: Darshan Sen <[email protected]>
1 parent 8180d30 commit ebcb9fe

File tree

4 files changed

+169
-0
lines changed

4 files changed

+169
-0
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"targets": [
3+
{
4+
"target_name": "dispatcher",
5+
"sources": [ "src/binding.cc" ],
6+
'cflags!': [ '-fno-exceptions' ],
7+
'cflags_cc!': [ '-fno-exceptions' ],
8+
'include_dirs': ["<!@(node -p \"require('node-addon-api').include\")"],
9+
'dependencies': ["<!(node -p \"require('node-addon-api').gyp\")"],
10+
'conditions': [
11+
['OS=="win"', {
12+
"msvs_settings": {
13+
"VCCLCompilerTool": {
14+
"ExceptionHandling": 1
15+
}
16+
}
17+
}],
18+
['OS=="mac"', {
19+
"xcode_settings": {
20+
"CLANG_CXX_LIBRARY": "libc++",
21+
'GCC_ENABLE_CPP_EXCEPTIONS': 'YES',
22+
'MACOSX_DEPLOYMENT_TARGET': '10.7'
23+
}
24+
}]
25+
]
26+
}
27+
]
28+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
'use strict'
2+
3+
const { EventEmitter } = require('node:events');
4+
const { dispatch } = require('bindings')('dispatcher');
5+
6+
class Socket extends EventEmitter {
7+
constructor () {
8+
super();
9+
dispatch(this.emit.bind(this));
10+
}
11+
}
12+
13+
const socket = new Socket();
14+
15+
socket.on('open', () => {
16+
console.log('opened');
17+
});
18+
19+
socket.on('message', (message) => {
20+
console.log(`message: ${message}`);
21+
});
22+
23+
socket.on('close', () => {
24+
console.log('closed');
25+
});
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "call-js-from-async-worker-execute",
3+
"version": "0.0.0",
4+
"description": "Node.js Addons - calls JS from AsyncWorker::Execute",
5+
"main": "index.js",
6+
"private": true,
7+
"gypfile": true,
8+
"scripts": {
9+
"start": "node index.js"
10+
},
11+
"dependencies": {
12+
"node-addon-api": "*",
13+
"bindings": "*"
14+
}
15+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#include <napi.h>
2+
3+
#include <chrono>
4+
#include <thread>
5+
6+
class Dispatcher : public Napi::AsyncWorker {
7+
public:
8+
Dispatcher(Napi::Env env, Napi::Function& callback)
9+
: Napi::AsyncWorker(env),
10+
callback_(callback),
11+
tsfn_{
12+
Napi::ThreadSafeFunction::New(env, callback, "Dispatcher", 0, 1)} {}
13+
14+
virtual ~Dispatcher() override { tsfn_.Release(); }
15+
16+
void Execute() override {
17+
// Since this method executes on a thread that is different from the main
18+
// thread, we can't directly call into JavaScript. To trigger a call into
19+
// JavaScript from this thread, ThreadSafeFunction needs to be used to
20+
// communicate with the main thread, so that the main thread can invoke the
21+
// JavaScript function.
22+
23+
napi_status status =
24+
tsfn_.BlockingCall([](Napi::Env env, Napi::Function js_callback) {
25+
js_callback.Call({Napi::String::New(env, "open")});
26+
});
27+
if (status != napi_ok) {
28+
SetError("Napi::ThreadSafeNapi::Function.BlockingCall() failed");
29+
return;
30+
}
31+
32+
std::this_thread::sleep_for(std::chrono::seconds(1));
33+
status = tsfn_.BlockingCall([](Napi::Env env, Napi::Function js_callback) {
34+
js_callback.Call(
35+
{Napi::String::New(env, "message"), Napi::String::New(env, "data1")});
36+
});
37+
if (status != napi_ok) {
38+
SetError("Napi::ThreadSafeNapi::Function.BlockingCall() failed");
39+
return;
40+
}
41+
42+
std::this_thread::sleep_for(std::chrono::seconds(1));
43+
status = tsfn_.BlockingCall([](Napi::Env env, Napi::Function js_callback) {
44+
js_callback.Call(
45+
{Napi::String::New(env, "message"), Napi::String::New(env, "data2")});
46+
});
47+
if (status != napi_ok) {
48+
SetError("Napi::ThreadSafeNapi::Function.BlockingCall() failed");
49+
return;
50+
}
51+
52+
std::this_thread::sleep_for(std::chrono::seconds(1));
53+
status = tsfn_.BlockingCall([](Napi::Env env, Napi::Function js_callback) {
54+
js_callback.Call(
55+
{Napi::String::New(env, "message"), Napi::String::New(env, "data3")});
56+
});
57+
if (status != napi_ok) {
58+
SetError("Napi::ThreadSafeNapi::Function.BlockingCall() failed");
59+
return;
60+
}
61+
62+
status = tsfn_.BlockingCall([](Napi::Env env, Napi::Function js_callback) {
63+
js_callback.Call({Napi::String::New(env, "close")});
64+
});
65+
if (status != napi_ok) {
66+
SetError("Napi::ThreadSafeNapi::Function.BlockingCall() failed");
67+
return;
68+
}
69+
}
70+
71+
void OnError(Napi::Error const& error) override {
72+
callback_.Call({Napi::String::New(Env(), "error"), error.Value()});
73+
}
74+
75+
private:
76+
Napi::Function callback_;
77+
Napi::ThreadSafeFunction tsfn_;
78+
};
79+
80+
void Dispatch(const Napi::CallbackInfo& info) {
81+
Napi::Env env = info.Env();
82+
83+
if (info.Length() < 1 || !info[0].IsFunction()) {
84+
Napi::TypeError::New(
85+
env, "The first argument needs to be the callback function.")
86+
.ThrowAsJavaScriptException();
87+
return;
88+
}
89+
90+
Napi::Function callback = info[0].As<Napi::Function>();
91+
92+
Dispatcher* dispatcher = new Dispatcher(env, callback);
93+
dispatcher->Queue();
94+
}
95+
96+
Napi::Object Init(Napi::Env env, Napi::Object exports) {
97+
exports.Set("dispatch", Napi::Function::New(env, Dispatch));
98+
return exports;
99+
}
100+
101+
NODE_API_MODULE(dispatcher, Init)

0 commit comments

Comments
 (0)