-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathhello_world.cpp
More file actions
278 lines (244 loc) · 7.99 KB
/
hello_world.cpp
File metadata and controls
278 lines (244 loc) · 7.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
#include "hello_world.hpp"
#include <exception>
#include <stdexcept>
#include <iostream>
#include <protozero/pbf_reader.hpp>
// Custom constructor added in order to test/cover throwing an error during initialization
HelloWorld::HelloWorld(std::string name) :
name_(name) {
if (name_ != "hello") {
throw std::runtime_error("name must be 'hello'");
}
}
/**
* Main class, called HelloWorld
* @class HelloWorld
* @example
* var HelloWorld = require('index.js');
* var HW = new HelloWorld();
* OR
* var HW = new HelloWorld('yo');
*/
NAN_METHOD(HelloWorld::New)
{
if (info.IsConstructCall())
{
try
{
if (info.Length() >= 1) {
if (info[0]->IsString())
{
std::string name = *v8::String::Utf8Value(info[0]->ToString());
auto *const self = new HelloWorld(name);
self->Wrap(info.This());
}
else
{
return Nan::ThrowTypeError(
"arg must be a string");
}
}
else {
auto *const self = new HelloWorld();
self->Wrap(info.This());
}
}
catch (const std::exception &ex)
{
return Nan::ThrowTypeError(ex.what());
}
info.GetReturnValue().Set(info.This());
}
else
{
return Nan::ThrowTypeError(
"Cannot call constructor as function, you need to use 'new' keyword");
}
}
Nan::Persistent<v8::Function> &HelloWorld::constructor()
{
static Nan::Persistent<v8::Function> init;
return init;
}
/*
* This is an internal function used to return callback error messages instead of
* throwing errors.
* Usage:
*
* v8::Local<v8::Function> callback;
* CallbackError("error message", callback);
* return; // this is important to prevent duplicate callbacks from being fired!
*/
inline void CallbackError(std::string message, v8::Local<v8::Function> callback) {
v8::Local<v8::Value> argv[1] = { Nan::Error(message.c_str()) };
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), callback, 1, argv);
}
/**
* Say howdy to the world
*
* @name wave
* @memberof HelloWorld
* @returns {String} a happy-go-lucky string saying hi
* @example
* var wave = HW.wave();
* console.log(wave); // => 'howdy world!'
*/
NAN_METHOD(HelloWorld::wave)
{
// info comes from the NAN_METHOD macro, which returns differently
// according to the version of node
info.GetReturnValue().Set(Nan::New<v8::String>("howdy world").ToLocalChecked());
}
/**
* Shout a phrase really loudly by adding an exclamation to the end, asynchronously
*
* @name shout
* @memberof HelloWorld
* @param {String} string - phrase to shout
* @param {Object} args - different ways to shout
* @param {boolean} args.louder - adds exclamation points to the string
* @param {Function} callback - from whence the shout comes, returns a string
* @example
* var HW = new HelloWorld();
* HW.shout('rawr', { louder: true }, function(err, shout) {
* if (err) throw err;
* console.log(shout); // => 'rawr!'
* });
*
*/
// this is the cpp object that will be passed around in 'shout' and callbacks
// referred to as a "baton"
class AsyncBaton
{
public:
AsyncBaton() :
request(),
cb(),
phrase(),
louder(false),
error_name(),
result() {}
uv_work_t request; // required
Nan::Persistent<v8::Function> cb; // callback function type
std::string phrase;
bool louder;
std::string error_name;
std::string result;
};
NAN_METHOD(HelloWorld::shout)
{
std::string phrase = "";
bool louder = false;
// check third argument, should be a 'callback' function.
// This allows us to set the callback so we can use it to return errors
// instead of throwing as well.
if (!info[2]->IsFunction())
{
Nan::ThrowTypeError("third arg 'callback' must be a function");
return;
}
v8::Local<v8::Function> callback = info[2].As<v8::Function>();
// check first argument, should be a 'phrase' string
if (!info[0]->IsString())
{
CallbackError("first arg 'phrase' must be a string", callback);
return;
}
phrase = *v8::String::Utf8Value((info[0])->ToString());
// check second argument, should be an 'options' object
if (!info[1]->IsObject())
{
CallbackError("second arg 'options' must be an object", callback);
return;
}
v8::Local<v8::Object> options = info[1].As<v8::Object>();
if (options->Has(Nan::New("louder").ToLocalChecked()))
{
v8::Local<v8::Value> louder_val = options->Get(Nan::New("louder").ToLocalChecked());
if (!louder_val->IsBoolean())
{
CallbackError("option 'louder' must be a boolean", callback);
return;
}
louder = louder_val->BooleanValue();
}
// set up the baton to pass into our threadpool
AsyncBaton *baton = new AsyncBaton();
baton->request.data = baton;
baton->phrase = phrase;
baton->louder = louder;
baton->cb.Reset(callback);
/*
`uv_queue_work` is the all-important way to pass info into the threadpool.
It cannot take v8 objects, so we need to do some manipulation above to convert into cpp objects
otherwise things get janky. It takes four arguments:
1) which loop to use, node only has one so we pass in a pointer to the default
2) the baton defined above, we use this to access information important for the method
3) operations to be executed within the threadpool
4) operations to be executed after #3 is complete to pass into the callback
*/
uv_queue_work(uv_default_loop(), &baton->request, AsyncShout, (uv_after_work_cb)AfterShout);
return;
}
std::string do_expensive_work(std::string const& phrase, bool louder) {
std::string result;
// This is purely for testing, to be able to simulate an unexpected throw
// from a function you do not control and may throw an exception
if (phrase != "rawr") {
throw std::runtime_error("we really would prefer rawr all the time");
}
result = phrase + "!";
if (louder)
{
result += "!!!!";
}
return result;
}
// this is where we actually exclaim our shout phrase
void HelloWorld::AsyncShout(uv_work_t* req)
{
AsyncBaton *baton = static_cast<AsyncBaton *>(req->data);
/***************** custom code here ******************/
// The try/catch is critical here: if code was added that could throw an unhandled error INSIDE the threadpool, it would be disasterous
try
{
baton->result = do_expensive_work(baton->phrase,baton->louder);
}
catch (std::exception const& ex)
{
baton->error_name = ex.what();
}
/***************** end custom code *******************/
}
// handle results from AsyncShout - if there are errors return those
// otherwise return the type & info to our callback
void HelloWorld::AfterShout(uv_work_t* req)
{
Nan::HandleScope scope;
AsyncBaton *baton = static_cast<AsyncBaton *>(req->data);
if (!baton->error_name.empty())
{
v8::Local<v8::Value> argv[1] = { Nan::Error(baton->error_name.c_str()) };
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(baton->cb), 1, argv);
}
else
{
v8::Local<v8::Value> argv[2] = { Nan::Null(), Nan::New<v8::String>(baton->result.data()).ToLocalChecked() };
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(baton->cb), 2, argv);
}
baton->cb.Reset();
delete baton;
}
NAN_MODULE_INIT(HelloWorld::Init)
{
const auto whoami = Nan::New("HelloWorld").ToLocalChecked();
auto fnTp = Nan::New<v8::FunctionTemplate>(New);
fnTp->InstanceTemplate()->SetInternalFieldCount(1);
fnTp->SetClassName(whoami);
// custom methods added here
SetPrototypeMethod(fnTp, "wave", wave);
SetPrototypeMethod(fnTp, "shout", shout);
const auto fn = Nan::GetFunction(fnTp).ToLocalChecked();
constructor().Reset(fn);
Nan::Set(target, whoami, fn);
}