Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adding an async function called "shout" #3

Merged
merged 11 commits into from
Jun 20, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions API.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# HelloWorld

Main class, called HelloWorld

**Examples**

```javascript
var HelloWorld = require('index.js');
var HW = new HelloWorld();
```

## wave

Say howdy to the world

**Examples**

```javascript
var wave = HW.wave();
console.log(wave); // => 'howdy world!'
```

Returns **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** a happy-go-lucky string saying hi

## shout

Shout a phrase really loudly by adding an exclamation to the end, asynchronously

**Parameters**

- `phrase` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** to shout
- `different` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** ways to shout
- `callback` **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)** from whence the shout comes, returns a string

**Examples**

```javascript
var HW = new HelloWorld();
HW.shout('rawr', {}, function(err, shout) {
if (err) throw err;
console.log(shout); // => 'rawr!'
});
```
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
default:
npm install --build-from-source

docs:
npm run docs
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"main": "./lib/index.js",
"scripts": {
"test": "tape test/*.test.js",
"install": "node-pre-gyp install --fallback-to-build"
"install": "node-pre-gyp install --fallback-to-build",
"docs": "documentation build src/*.cpp --polyglot -f md -o API.md"
},
"author": "Mapbox",
"license": "ISC",
Expand All @@ -14,6 +15,7 @@
"node-pre-gyp": "^0.6.28"
},
"devDependencies": {
"documentation": "^4.0.0-beta5",
"tape": "^4.5.1"
},
"binary": {
Expand Down
173 changes: 163 additions & 10 deletions src/hello_world.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
#include <exception>
#include <iostream>

/**
* Main class, called HelloWorld
* @class HelloWorld
* @example
* var HelloWorld = require('index.js');
* var HW = new HelloWorld();
*/
NAN_METHOD(HelloWorld::New)
{
if (info.IsConstructCall())
Expand All @@ -26,17 +33,166 @@ NAN_METHOD(HelloWorld::New)
}
}

Nan::Persistent<v8::Function> &HelloWorld::constructor()
{
static Nan::Persistent<v8::Function> init;
return init;
}

/**
* 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());
info.GetReturnValue().Set(Nan::New<v8::String>("howdy world").ToLocalChecked());
}

Nan::Persistent<v8::Function> &HelloWorld::constructor()
/**
* Shout a phrase really loudly by adding an exclamation to the end, asynchronously
*
* @name shout
* @memberof HelloWorld
* @param {String} phrase to shout
* @param {Object} different ways to shout
* @param {Function} callback - from whence the shout comes, returns a string
* @example
* var HW = new HelloWorld();
* HW.shout('rawr', {}, 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
{
static Nan::Persistent<v8::Function> init;
return init;
public:
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 first argument, should be a 'phrase' string
if (!info[0]->IsString())
{
Nan::ThrowTypeError("first arg 'phrase' must be a string");
return;
}
phrase = *v8::String::Utf8Value((info[0])->ToString());

// check second argument, should be an 'options' object
if (!info[1]->IsObject())
{
Nan::ThrowTypeError("second arg 'options' must be an object");
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())
{
Nan::ThrowError("option 'louder' must be a boolean");
return;
}
louder = louder_val->BooleanValue();
}

// check third argument, should be a 'callback' function
if (!info[2]->IsFunction())
{
Nan::ThrowTypeError("third arg 'callback' must be a function");
return;
}
v8::Local<v8::Value> callback = info[2];

// 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.As<v8::Function>());

/*
`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;
}

// 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 ******************/
try
{
std::string return_string = baton->phrase + "!";

if (baton->louder)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@springmeyer seeing that the return_string now comes back empty for this method after wrapping it all within the try/catch. Seems odd, but if I pull it out:

/***************** custom code here ******************/

std::string return_string = baton->phrase + "!";

if (baton->louder)
{
    return_string += "!!!!";
}

/***************** end custom code *******************/

try
{
    baton->result = return_string;
}
catch (std::exception const& ex)
{
    baton->error_name = ex.what();
}

things work fine

{
return_string += "!!!!";
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, I see what the issue was @springmeyer - we lost the baton->result = return_string here. Adding it back in does the trick.


baton->result = return_string;
}
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)
Expand All @@ -47,16 +203,13 @@ NAN_MODULE_INIT(HelloWorld::Init)
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);
}

NODE_MODULE(HelloWorld, HelloWorld::Init);



NODE_MODULE(HelloWorld, HelloWorld::Init);
11 changes: 8 additions & 3 deletions src/hello_world.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,15 @@ class HelloWorld: public Nan::ObjectWrap
// initializer
static NAN_MODULE_INIT(Init);

// method new used for the constructor
// methods required for the constructor
static NAN_METHOD(New);
static Nan::Persistent<v8::Function> &constructor();

// custom method called wave
// wave, custom sync method
static NAN_METHOD(wave);
static Nan::Persistent<v8::Function> &constructor();

// shout, custom async method
static NAN_METHOD(shout);
static void AsyncShout(uv_work_t* req);
static void AfterShout(uv_work_t* req);
};
Empty file removed src/hey.cpp
Empty file.
66 changes: 62 additions & 4 deletions test/hello_world.test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,67 @@
var test = require('tape');
var HelloWorld = require('../lib/index.js');
var HW = new HelloWorld();

test('wave hi', function(t) {
var HW = new HelloWorld();
test('wave success', function(t) {
var hello = HW.wave();
t.equal(hello, 'howdy world!', 'output of HelloWorld.wave');
t.equal(hello, 'howdy world', 'output of HelloWorld.wave');
t.end();
})
});

test('shout success', function(t) {
HW.shout('rawr', {}, function(err, shout) {
if (err) throw err;
t.equal(shout, 'rawr!');
t.end();
});
});

test('shout success - options.louder', function(t) {
HW.shout('rawr', { louder: true }, function(err, shout) {
if (err) throw err;
t.equal(shout, 'rawr!!!!!');
t.end();
});
});

// we have to wrap these in try/catch statements right now
// https://github.com/mapbox/node-cpp-skel/issues/4
test('shout error - non string phrase', function(t) {
try {
HW.shout(4, {}, function(err, shout) {});
} catch (err) {
t.ok(err, 'expected error');
t.ok(err.message.indexOf('phrase') > -1, 'proper error message');
t.end();
}
});

test('shout error - no options object', function(t) {
try {
HW.shout('rawr', true, function(err, shout) {});
} catch (err) {
t.ok(err, 'expected error');
t.ok(err.message.indexOf('options') > -1, 'proper error message');
t.end();
}
});

test('shout error - options.louder non boolean', function(t) {
try {
HW.shout('rawr', { louder: 3 }, function(err, shout) {});
} catch (err) {
t.ok(err, 'expected error');
t.ok(err.message.indexOf('louder') > -1, 'proper error message');
t.end();
}
});

test('shout error - no callback', function(t) {
try {
HW.shout('rawr', {});
} catch (err) {
t.ok(err, 'expected error');
t.ok(err.message.indexOf('callback') > -1, 'proper error message');
t.end();
}
});