diff --git a/README.md b/README.md
index b3a97d0ac..ee673a972 100644
--- a/README.md
+++ b/README.md
@@ -93,6 +93,7 @@ still a work in progress as its not yet complete).
- [Memory Management](doc/memory_management.md)
- [Async Operations](doc/async_operations.md)
- [AsyncWorker](doc/async_worker.md)
+ - [AsyncContext](doc/async_context.md)
- [Promises](doc/promises.md)
diff --git a/doc/async_context.md b/doc/async_context.md
new file mode 100644
index 000000000..a40813b8d
--- /dev/null
+++ b/doc/async_context.md
@@ -0,0 +1,149 @@
+# AsyncContext
+
+The [AsyncWorker](async_worker.md) class may not be appropriate for every scenario, because with
+those the async execution still happens on the main event loop. When using any
+other async mechanism, introducing a new class `AsyncContext` is necessary to
+ensure an async operation is properly tracked by the runtime.
+The `AsyncContext` class provides `MakeCallback()` method to properly restore
+the correct async execution context.
+
+## Methods
+
+### MakeCallback
+
+This method is used to call from native code back into JavaScript after
+returning from an async operation (when there is no other script on the stack).
+
+```cpp
+Value MakeCallback() const
+```
+
+Returns a `Value` representing the JavaScript object returned.
+
+### MakeCallback
+
+This method is used to call from native code back into JavaScript after
+returning from an async operation (when there is no other script on the stack).
+
+```cpp
+Value MakeCallback(const Object& receiver) const
+```
+
+- `[in] receiver`: The `this` object passed to the callback.
+
+Returns a `Value` representing the JavaScript object returned.
+
+### MakeCallback
+
+This method is used to call from native code back into JavaScript after
+returning from an async operation (when there is no other script on the stack).
+
+```cpp
+Value MakeCallback(const std::initializer_list& args) const
+```
+
+- `[in] args`: Initializer list of JavaScript values as `napi_value`
+representing the arguments to the callback.
+
+Returns a `Value` representing the JavaScript object returned.
+
+### MakeCallback
+
+This method is used to call from native code back into JavaScript after
+returning from an async operation (when there is no other script on the stack).
+
+```cpp
+Value MakeCallback(const Object& receiver, const std::initializer_list& args) const
+```
+
+- `[in] receiver`: The `this` object passed to the callback.
+- `[in] args`: Initializer list of JavaScript values as `napi_value`
+representing the arguments to the callback.
+
+Returns a `Value` representing the JavaScript object returned.
+
+### Constructor
+
+Creates a new `AsyncContext`.
+
+```cpp
+explicit AsyncContext(const char* resource_name, const Function& callback);
+```
+
+- `[in] resource_name`: Null-terminated strings that represents the
+identifier for the kind of resource that is being provided for diagnostic
+information exposed by the async_hooks API.
+- `[in] callback`: The function which will be called when an asynchronous
+operations ends.
+
+Returns an AsyncContext instance which can later make the given callback by
+`MakeCallback()` method.
+
+### Constructor
+
+Creates a new `AsyncContext`.
+
+```cpp
+explicit AsyncContext(const char* resource_name, const Object& resource, const Function& callback);
+```
+
+- `[in] resource_name`: Null-terminated strings that represents the
+identifier for the kind of resource that is being provided for diagnostic
+information exposed by the async_hooks API.
+- `[in] resource`: Object associated with the asynchronous operation that
+will be passed to possible async_hooks.
+- `[in] callback`: The function which will be called when an asynchronous
+operations ends.
+
+Returns an AsyncContext instance which can later make the given callback by
+`MakeCallback()` method.
+
+### Destructor
+
+The async context to be destroyed.
+
+```cpp
+virtual ~AsyncContext();
+```
+
+## Example
+
+```cpp
+#include "napi.h"
+#include "uv.h"
+
+using namespace Napi;
+
+void HeavyWork(uv_work_t* request) {
+ ...
+}
+
+void DidHeavyWork(uv_work_t* request, int status) {
+ HandleScope scope;
+ AsyncContext* context = static_cast(request->data);
+
+ // Run the callback in the async context.
+ context->MakeCallback();
+
+ // The async context is destroyed.
+ delete context;
+}
+
+void RunAsync(const CallbackInfo& info) {
+ Function callback = info[0].As();
+
+ // Creat a new async context instance.
+ AsyncContext* new AsyncContext("TestResource", callback);
+
+ // You can queue a task with the async context to the custom async mechanism.
+ // This example is using `libuv` as custom async mechanism but it can be
+ // anything.
+ uv_work_t* request = new uv_work_t();
+ request->data = context;
+ uv_queue_work(
+ uv_default_loop(),
+ request,
+ HeavyWork,
+ reinterpret_cast(DidHeavyWork));
+}
+```
diff --git a/doc/async_operations.md b/doc/async_operations.md
index b8dec37cf..019feb0f6 100644
--- a/doc/async_operations.md
+++ b/doc/async_operations.md
@@ -21,3 +21,9 @@ asynchronous operations:
These class helps manage asynchronous operations through an abstraction
of the concept of moving data between the **event loop** and **worker threads**.
+
+Also, the above class may not be appropriate for every scenario. When using any
+other asynchronous mechanism, the following API is necessary to ensure an
+asynchronous operation is properly tracked by the runtime:
+
+- **[AsyncContext](async_context.md)**
diff --git a/napi-inl.h b/napi-inl.h
index 46ba2c0b9..2a036f64c 100644
--- a/napi-inl.h
+++ b/napi-inl.h
@@ -3074,6 +3074,73 @@ inline Value EscapableHandleScope::Escape(napi_value escapee) {
return Value(_env, result);
}
+////////////////////////////////////////////////////////////////////////////////
+// AsyncContext class
+////////////////////////////////////////////////////////////////////////////////
+
+inline AsyncContext::AsyncContext(const char* resource_name,
+ const Function& callback)
+ : AsyncContext(resource_name, Object::New(callback.Env()), callback) {
+}
+
+inline AsyncContext::AsyncContext(const char* resource_name,
+ const Object& resource,
+ const Function& callback)
+ : _env(callback.Env()),
+ _context(nullptr),
+ _callback(Napi::Persistent(callback)) {
+ napi_value resource_id;
+ napi_status status = napi_create_string_utf8(
+ _env, resource_name, NAPI_AUTO_LENGTH, &resource_id);
+ NAPI_THROW_IF_FAILED(_env, status);
+
+ status = napi_async_init(_env, resource, resource_id, &_context);
+ NAPI_THROW_IF_FAILED(_env, status);
+}
+
+inline AsyncContext::~AsyncContext() {
+ if (_context != nullptr) {
+ napi_async_destroy(_env, _context);
+ _context = nullptr;
+ }
+}
+
+inline AsyncContext::AsyncContext(AsyncContext&& other) {
+ _env = other._env;
+ other._env = nullptr;
+ _context = other._context;
+ other._context = nullptr;
+ _callback = std::move(other._callback);
+}
+
+inline AsyncContext& AsyncContext::operator =(AsyncContext&& other) {
+ _env = other._env;
+ other._env = nullptr;
+ _context = other._context;
+ other._context = nullptr;
+ _callback = std::move(other._callback);
+ return *this;
+}
+
+inline Value AsyncContext::MakeCallback() const {
+ return MakeCallback(Object::New(_env));
+}
+
+inline Value AsyncContext::MakeCallback(const Object& receiver) const {
+ return _callback.MakeCallback(receiver, std::initializer_list{});
+}
+
+inline Value AsyncContext::MakeCallback(
+ const std::initializer_list& args) const {
+ return MakeCallback(Object::New(_env), args);
+}
+
+inline Value AsyncContext::MakeCallback(
+ const Object& receiver,
+ const std::initializer_list& args) const {
+ return _callback.MakeCallback(receiver, args);
+}
+
////////////////////////////////////////////////////////////////////////////////
// AsyncWorker class
////////////////////////////////////////////////////////////////////////////////
diff --git a/napi.h b/napi.h
index f9852968b..d13a6d1a8 100644
--- a/napi.h
+++ b/napi.h
@@ -1495,6 +1495,31 @@ namespace Napi {
napi_escapable_handle_scope _scope;
};
+ class AsyncContext {
+ public:
+ explicit AsyncContext(const char* resource_name, const Function& callback);
+ explicit AsyncContext(const char* resource_name,
+ const Object& resource,
+ const Function& callback);
+ virtual ~AsyncContext();
+
+ AsyncContext(AsyncContext&& other);
+ AsyncContext& operator =(AsyncContext&& other);
+ AsyncContext(const AsyncContext&) = delete;
+ AsyncContext& operator =(AsyncContext&) = delete;
+
+ Value MakeCallback() const;
+ Value MakeCallback(const Object& receiver) const;
+ Value MakeCallback(const std::initializer_list& args) const;
+ Value MakeCallback(const Object& receiver,
+ const std::initializer_list& args) const;
+
+ private:
+ napi_env _env;
+ napi_async_context _context;
+ FunctionReference _callback;
+ };
+
class AsyncWorker {
public:
virtual ~AsyncWorker();
diff --git a/test/asynccontext.cc b/test/asynccontext.cc
new file mode 100644
index 000000000..1375615b3
--- /dev/null
+++ b/test/asynccontext.cc
@@ -0,0 +1,26 @@
+#include "napi.h"
+
+using namespace Napi;
+
+namespace {
+
+static AsyncContext* context;
+
+static void CreateAsyncContext(const CallbackInfo& info) {
+ Function callback = info[0].As();
+ context = new AsyncContext("TestResource", callback);
+}
+
+static void MakeCallback(const CallbackInfo& info) {
+ context->MakeCallback();
+ delete context;
+}
+
+} // end anonymous namespace
+
+Object InitAsyncContext(Env env) {
+ Object exports = Object::New(env);
+ exports["createAsyncContext"] = Function::New(env, CreateAsyncContext);
+ exports["makeCallback"] = Function::New(env, MakeCallback);
+ return exports;
+}
diff --git a/test/asynccontext.js b/test/asynccontext.js
new file mode 100644
index 000000000..fe5a7e4a9
--- /dev/null
+++ b/test/asynccontext.js
@@ -0,0 +1,16 @@
+'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 called = false;
+ binding.asynccontext.createAsyncContext(function() {
+ called = true;
+ });
+ assert.strictEqual(called, false);
+ binding.asynccontext.makeCallback();
+ assert.strictEqual(called, true);
+}
diff --git a/test/binding.cc b/test/binding.cc
index 0032c8e30..1a67a31e1 100644
--- a/test/binding.cc
+++ b/test/binding.cc
@@ -3,6 +3,7 @@
using namespace Napi;
Object InitArrayBuffer(Env env);
+Object InitAsyncContext(Env env);
Object InitAsyncWorker(Env env);
Object InitBasicTypesNumber(Env env);
Object InitBasicTypesValue(Env env);
@@ -22,6 +23,7 @@ Object InitObjectWrap(Env env);
Object Init(Env env, Object exports) {
exports.Set("arraybuffer", InitArrayBuffer(env));
+ exports.Set("asynccontext", InitAsyncContext(env));
exports.Set("asyncworker", InitAsyncWorker(env));
exports.Set("basic_types_number", InitBasicTypesNumber(env));
exports.Set("basic_types_value", InitBasicTypesValue(env));
diff --git a/test/binding.gyp b/test/binding.gyp
index acbbc0a12..291100ff8 100644
--- a/test/binding.gyp
+++ b/test/binding.gyp
@@ -2,6 +2,7 @@
'target_defaults': {
'sources': [
'arraybuffer.cc',
+ 'asynccontext.cc',
'asyncworker.cc',
'basic_types/number.cc',
'basic_types/value.cc',
diff --git a/test/index.js b/test/index.js
index c9d7fb0c2..23f31f034 100644
--- a/test/index.js
+++ b/test/index.js
@@ -9,6 +9,7 @@ process.config.target_defaults.default_configuration =
// explicit declaration as follows.
let testModules = [
'arraybuffer',
+ 'asynccontext',
'asyncworker',
'basic_types/number',
'basic_types/value',