Skip to content

Commit 2c4e2e1

Browse files
author
Carol Hansen
authored
Add object-sync example (#48)
* sheepishly starting object-sync example * add hello function to object example...not compiling * add object tests, fix static issue and remove hello function collision * add some questions * add constructor error handling, remove unneeded NAN_MODULE_INIT, and add plenty of contextual comments around init * attempt adding a constructor that sets member var * use direct initialization * unwrap the object to access name_ * add test for custom constructor * match sync examples * name the persistant creator 'create_once' to avoid name clash with c++ constructor term * Add HandleScope where needed / comments for when not needed explicitly
1 parent 493aeba commit 2c4e2e1

File tree

9 files changed

+295
-118
lines changed

9 files changed

+295
-118
lines changed

binding.gyp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
'./src/module.cpp',
3838
'./src/standalone/hello.cpp',
3939
'./src/standalone_async/hello_async.cpp',
40-
'./src/hello_world.cpp'
40+
'./src/object_sync/hello.cpp'
4141
],
4242
'include_dirs': [
4343
'<!(node -e \'require("nan")\')'

src/hello_world.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ NAN_METHOD(HelloWorld::New)
4444
}
4545
else {
4646
auto *const self = new HelloWorld();
47-
self->Wrap(info.This());
47+
self->Wrap(info.This()); // Connects C++ object to Javascript object (this)
4848
}
4949

5050
}
@@ -64,6 +64,7 @@ NAN_METHOD(HelloWorld::New)
6464

6565
Nan::Persistent<v8::Function> &HelloWorld::constructor()
6666
{
67+
6768
static Nan::Persistent<v8::Function> init;
6869
return init;
6970
}
@@ -260,6 +261,7 @@ void HelloWorld::AfterShout(uv_work_t* req)
260261
delete baton;
261262
}
262263

264+
// called when "require" in Javascript world
263265
NAN_MODULE_INIT(HelloWorld::Init)
264266
{
265267
const auto whoami = Nan::New("HelloWorld").ToLocalChecked();
@@ -271,8 +273,8 @@ NAN_MODULE_INIT(HelloWorld::Init)
271273
// custom methods added here
272274
SetPrototypeMethod(fnTp, "wave", wave);
273275
SetPrototypeMethod(fnTp, "shout", shout);
274-
276+
275277
const auto fn = Nan::GetFunction(fnTp).ToLocalChecked();
276-
constructor().Reset(fn);
278+
constructor().Reset(fn); // calling the static &HelloWorld constructor method
277279
Nan::Set(target, whoami, fn);
278280
}

src/module.cpp

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
#include <nan.h>
22
#include "standalone/hello.hpp"
33
#include "standalone_async/hello_async.hpp"
4-
#include "hello_world.hpp"
4+
#include "object_sync/hello.hpp"
55
// #include "your_code.hpp"
66

77
// "target" is a magic var that nodejs passes into modules scope
8-
// When you write things to target, they become available to call from js
8+
// When you write things to target, they become available to call from Javascript world
99
static void init(v8::Local<v8::Object> target) {
1010

1111
// expose hello method
@@ -14,11 +14,18 @@ static void init(v8::Local<v8::Object> target) {
1414
// expose hello_async method
1515
Nan::SetMethod(target, "hello_async", standalone_async::hello_async);
1616

17-
// expose hello_world class
18-
HelloWorld::Init(target);
17+
// expose HelloObject class
18+
object_sync::HelloObject::Init(target);
19+
20+
/**
21+
* You may have noticed there are multiple "hello" functions as part of this module.
22+
* They are both exposed a bit differently.
23+
* 1) standalone::hello // exposed above
24+
* 2) HelloObject.hello // exposed in object_sync/hello.cpp as part of HelloObject
25+
*/
1926

20-
// add more methods/classes below that youd like to use in node.js-world
21-
// then create a .cpp and .hpp file in /src for each new method
27+
// add more methods/classes below that youd like to use in Javascript world
28+
// then create a directory in /src with a .cpp and a .hpp file
2229
}
2330

2431
NODE_MODULE(module, init)

src/object_sync/hello.cpp

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
#include "hello.hpp"
2+
3+
// If this was not defined within a namespace, it would be in the global scope.
4+
namespace object_sync {
5+
6+
// Custom constructor, assigns custom name passed in from Javascript world.
7+
// This constructor uses member init list via the semicolon, aka "direct initialization"
8+
// which is more efficient than using assignment operators.
9+
HelloObject::HelloObject(std::string name) :
10+
name_(name) {}
11+
12+
// Triggered from Javascript world when calling "new HelloObject()" or "new HelloObject(name)"
13+
NAN_METHOD(HelloObject::New) {
14+
if (info.IsConstructCall())
15+
{
16+
try
17+
{
18+
if (info.Length() >= 1) {
19+
if (info[0]->IsString())
20+
{
21+
std::string name = *v8::String::Utf8Value(info[0]->ToString());
22+
auto *const self = new HelloObject(name);
23+
self->Wrap(info.This());
24+
}
25+
else
26+
{
27+
return Nan::ThrowTypeError(
28+
"arg must be a string");
29+
}
30+
}
31+
else {
32+
auto *const self = new HelloObject();
33+
self->Wrap(info.This());
34+
}
35+
36+
}
37+
catch (const std::exception &ex)
38+
{
39+
return Nan::ThrowTypeError(ex.what());
40+
}
41+
42+
info.GetReturnValue().Set(info.This());
43+
}
44+
else
45+
{
46+
return Nan::ThrowTypeError(
47+
"Cannot call constructor as function, you need to use 'new' keyword");
48+
}
49+
}
50+
51+
// If this function was not defined within a namespace, it would be in the global scope.
52+
// NAN_METHOD is applicable to methods you want to expose to JS world
53+
NAN_METHOD(HelloObject::hello) {
54+
// Note: a HandleScope is automatically included inside NAN_METHOD (See the docs at NAN that say:
55+
// 'Note that an implicit HandleScope is created for you on JavaScript-accessible methods so you do not need to insert one yourself.'
56+
// at https://github.com/nodejs/nan/blob/2dfc5c2d19c8066903a19ced6a72c06d2c825dec/doc/scopes.md#nanhandlescope
57+
58+
HelloObject* h = Nan::ObjectWrap::Unwrap<HelloObject>(info.Holder());
59+
60+
// "info" comes from the NAN_METHOD macro, which returns differently
61+
// according to the version of node
62+
info.GetReturnValue().Set(Nan::New<v8::String>("...initialized an object...hello " + h->name_).ToLocalChecked()); // ???
63+
64+
}
65+
66+
// Singleton...not really sure what to say about this
67+
Nan::Persistent<v8::Function> &HelloObject::create_once()
68+
{
69+
static Nan::Persistent<v8::Function> init;
70+
return init;
71+
}
72+
73+
void HelloObject::Init(v8::Local<v8::Object> target)
74+
{
75+
// A handlescope is needed so that v8 objects created in the local memory space (this function in this case)
76+
// are cleaned up when the function is done running (and the handlescope is destroyed)
77+
// Fun trivia: forgetting a handlescope is one of the most common causes of memory leaks in node.js core
78+
// https://www.joyent.com/blog/walmart-node-js-memory-leak
79+
Nan::HandleScope scope;
80+
81+
// This is saying:
82+
// "Node, please allocate a new Javascript string object
83+
// inside the V8 local memory space, with the value 'HelloObject' "
84+
v8::Local<v8::String> whoami = Nan::New("HelloObject").ToLocalChecked();
85+
86+
// Create the HelloObject
87+
auto fnTp = Nan::New<v8::FunctionTemplate>(HelloObject::New); // Passing the HelloObject::New method above
88+
fnTp->InstanceTemplate()->SetInternalFieldCount(1); // It's 1 when holding the ObjectWrap itself and nothing else
89+
fnTp->SetClassName(whoami); // Passing the Javascript string object above
90+
91+
// Add custom methods here.
92+
// This is how hello() is exposed as part of HelloObject.
93+
// This line is attaching the "hello" method to a JavaScript function prototype.
94+
// "hello" is therefore like a property of the fnTp object
95+
// ex: console.log(HelloObject.hello) --> [Function: hello]
96+
SetPrototypeMethod(fnTp, "hello", hello);
97+
98+
// Create an unique instance of the HelloObject function template,
99+
// then set this unique instance to the target
100+
const auto fn = Nan::GetFunction(fnTp).ToLocalChecked();
101+
create_once().Reset(fn); // calls the static &HelloObject::constructor method above. This ensures the instructions in this Init function are retained in memory even after this code block ends.
102+
Nan::Set(target, whoami, fn);
103+
}
104+
105+
} // namespace object_sync

src/object_sync/hello.hpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#pragma once
2+
#include <nan.h>
3+
4+
namespace object_sync {
5+
6+
/**
7+
* HelloObject class
8+
* This is in a header file so we can access it across other .cpp files if necessary
9+
* Also, this class adheres to the rule of Zero because we define no custom destructor or copy constructor
10+
*/
11+
class HelloObject: public Nan::ObjectWrap {
12+
13+
public:
14+
// initializer
15+
static void Init(v8::Local<v8::Object> target);
16+
17+
// methods required for the V8 constructor (?)
18+
static NAN_METHOD(New);
19+
static Nan::Persistent<v8::Function> &create_once();
20+
21+
// hello, custom sync method tied to Init of this class
22+
// method's logic lives in ./hello.cpp
23+
static NAN_METHOD(hello);
24+
25+
// C++ Constructor
26+
// This includes a Default Argument
27+
// If a parameter value is passed in, it takes priority over the default arg
28+
HelloObject(std::string name="world");
29+
30+
private:
31+
// member variable
32+
// specific to each instance of the class
33+
std::string name_;
34+
35+
};
36+
37+
}

src/standalone/hello.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ namespace standalone {
1717

1818
// "info" comes from the NAN_METHOD macro, which returns differently
1919
// according to the version of node
20-
info.GetReturnValue().Set(Nan::New<v8::String>("world").ToLocalChecked());
20+
info.GetReturnValue().Set(Nan::New<v8::String>("hello world").ToLocalChecked());
2121

2222
}
2323
} // namespace standalone

test/hello.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ var module = require('../lib/index.js');
33

44
test('prints world', function(t) {
55
var check = module.hello();
6-
t.equal(check, 'world', 'returned world');
6+
t.equal(check, 'hello world', 'returned world');
77
t.end();
88
});
99

test/hello_object.test.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
var test = require('tape');
2+
var module = require('../lib/index.js');
3+
4+
test('success: prints expected string', function(t) {
5+
var H = new module.HelloObject();
6+
var check = H.hello();
7+
t.equal(check, '...initialized an object...hello world', 'returned expected string');
8+
t.end();
9+
});
10+
11+
test('success: prints expected string', function(t) {
12+
var H = new module.HelloObject('carol');
13+
var check = H.hello();
14+
t.equal(check, '...initialized an object...hello carol', 'returned expected string');
15+
t.end();
16+
});
17+
18+
test('error: throws when missing "new"', function(t) {
19+
try {
20+
var H = module.HelloObject();
21+
} catch(err) {
22+
t.ok(err);
23+
t.equal(err.message, 'Cannot call constructor as function, you need to use \'new\' keyword', 'expected error message')
24+
t.end();
25+
}
26+
});

0 commit comments

Comments
 (0)