Skip to content

Commit

Permalink
Implement AsyncResolver on top of Promises.
Browse files Browse the repository at this point in the history
  • Loading branch information
rolftimmermans committed Sep 14, 2017
1 parent 627f537 commit 5dddef4
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 0 deletions.
93 changes: 93 additions & 0 deletions napi-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -2823,6 +2823,99 @@ inline void AsyncWorker::OnWorkComplete(
delete self;
}

////////////////////////////////////////////////////////////////////////////////
// AsyncResolver class
////////////////////////////////////////////////////////////////////////////////

inline AsyncResolver::AsyncResolver(const Napi::Env& env)
: _env(env), _resolver(env) {
napi_status status = napi_create_async_work(
_env, OnExecute, OnWorkComplete, this, &_work);
NAPI_THROW_IF_FAILED(_env, status);
}

inline AsyncResolver::~AsyncResolver() {
if (_work != nullptr) {
napi_delete_async_work(_env, _work);
_work = nullptr;
}
}

inline AsyncResolver::AsyncResolver(AsyncResolver&& other)
: _resolver(std::move(other._resolver)) {
_env = other._env;
other._env = nullptr;
_work = other._work;
other._work = nullptr;
_error = std::move(other._error);
}

inline AsyncResolver& AsyncResolver::operator =(AsyncResolver&& other) {
_env = other._env;
other._env = nullptr;
_work = other._work;
other._work = nullptr;
_resolver = std::move(other._resolver);
_error = std::move(other._error);
return *this;
}

inline AsyncResolver::operator napi_async_work() const {
return _work;
}

inline Napi::Env AsyncResolver::Env() const {
return Napi::Env(_env);
}

inline void AsyncResolver::Queue() {
napi_status status = napi_queue_async_work(_env, _work);
NAPI_THROW_IF_FAILED(_env, status);
}

inline Napi::Promise AsyncResolver::Promise() {
return _resolver.Promise();
}

inline void AsyncResolver::SetError(const std::string& error) {
_error = error;
}

inline Value AsyncResolver::GetValue() {
return Env().Undefined();
}

inline Error AsyncResolver::GetError() {
return Error::New(_env, _error);
}

inline void AsyncResolver::OnExecute(napi_env env, void* this_pointer) {
AsyncResolver* self = static_cast<AsyncResolver*>(this_pointer);
#ifdef NAPI_CPP_EXCEPTIONS
try {
self->Execute();
} catch (const std::exception& e) {
self->SetError(e.what());
}
#else // NAPI_CPP_EXCEPTIONS
self->Execute();
#endif // NAPI_CPP_EXCEPTIONS
}

inline void AsyncResolver::OnWorkComplete(
napi_env env, napi_status status, void* this_pointer) {
AsyncResolver* self = static_cast<AsyncResolver*>(this_pointer);
if (status != napi_cancelled) {
HandleScope scope(self->_env);
if (self->_error.size() == 0) {
self->_resolver.Resolve(self->GetValue());
} else {
self->_resolver.Reject(self->GetError().Value());
}
}
delete self;
}

// These macros shouldn't be useful in user code.
#undef NAPI_THROW
#undef NAPI_THROW_IF_FAILED
Expand Down
41 changes: 41 additions & 0 deletions napi.h
Original file line number Diff line number Diff line change
Expand Up @@ -1465,6 +1465,47 @@ namespace Napi {
std::string _error;
};

class AsyncResolver {
public:
virtual ~AsyncResolver();

// An async promise can be moved but cannot be copied.
AsyncResolver(AsyncResolver&& other);
AsyncResolver& operator =(AsyncResolver&& other);
AsyncResolver(const AsyncResolver&) = delete;
AsyncResolver& operator =(AsyncResolver&) = delete;

operator napi_async_work() const;

Napi::Env Env() const;

void Queue();
Napi::Promise Promise();

protected:
AsyncResolver(const Napi::Env& env);

virtual void Execute() = 0;
virtual Value GetValue();
virtual Error GetError();

void SetError(const std::string& error);

private:
void Resolve(Value value);
void Reject(const Error& err);

static void OnExecute(napi_env env, void* this_pointer);
static void OnWorkComplete(napi_env env,
napi_status status,
void* this_pointer);

napi_env _env;
napi_async_work _work;
Promise::Resolver _resolver;
std::string _error;
};

} // namespace Napi

// Inline implementations of all the above class methods are included here.
Expand Down
36 changes: 36 additions & 0 deletions test/asyncresolver.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#include "napi.h"

using namespace Napi;

class TestResolver : public AsyncResolver {
public:
static Value DoWork(const CallbackInfo& info) {
TestResolver* resolver = new TestResolver(info.Env());
resolver->_succeed = info[0].As<Boolean>();
resolver->_data = Persistent(info[1]);
resolver->Queue();
return resolver->Promise();
}

protected:
void Execute() override {
if (!_succeed) {
SetError("test error");
}
}

Value GetValue() override {
return _data.Value();
}

private:
TestResolver(const Napi::Env& env) : AsyncResolver(env) {}
bool _succeed;
Reference<Value> _data;
};

Object InitAsyncResolver(Env env) {
Object exports = Object::New(env);
exports["doWork"] = Function::New(env, TestResolver::DoWork);
return exports;
}
31 changes: 31 additions & 0 deletions test/asyncresolver.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
'use strict';
const buildType = process.config.target_defaults.default_configuration;
const assert = require('assert');

test(require(`./build/${buildType}/binding.node`));
test(require(`./build/${buildType}/binding_noexcept.node`));

function test(binding) {
let resolved = null;
binding.asyncresolver.doWork(true, 'test data').then(function(data) {
resolved = data;
}).catch(function(err) {
assert.ok(false);
});

assert.strictEqual(resolved, null);
setImmediate(() => {
assert.strictEqual(resolved, 'test data');
});

let rejected = null;
binding.asyncresolver.doWork(false, 'test data').catch(function(err) {
rejected = err;
});

assert.strictEqual(rejected, null);
setImmediate(() => {
assert.ok(rejected instanceof Error);
assert.strictEqual(rejected.message, 'test error');
});
}
2 changes: 2 additions & 0 deletions test/binding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using namespace Napi;

Object InitArrayBuffer(Env env);
Object InitAsyncResolver(Env env);
Object InitAsyncWorker(Env env);
Object InitBuffer(Env env);
Object InitError(Env env);
Expand All @@ -15,6 +16,7 @@ Object InitTypedArray(Env env);

void Init(Env env, Object exports, Object module) {
exports.Set("arraybuffer", InitArrayBuffer(env));
exports.Set("asyncresolver", InitAsyncResolver(env));
exports.Set("asyncworker", InitAsyncWorker(env));
exports.Set("buffer", InitBuffer(env));
exports.Set("error", InitError(env));
Expand Down
1 change: 1 addition & 0 deletions test/binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
'target_defaults': {
'sources': [
'arraybuffer.cc',
'asyncresolver.cc',
'asyncworker.cc',
'binding.cc',
'buffer.cc',
Expand Down
1 change: 1 addition & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

let testModules = [
'arraybuffer',
'asyncresolver',
'asyncworker',
'buffer',
'error',
Expand Down

0 comments on commit 5dddef4

Please sign in to comment.