node-addon-api
repository·main·Indexed 25 days ago
https://github.com/nodejs/node-addon-apiA collection of header-only C++ wrapper classes for the C-based Node-API. It provides a C++ object model and exception handling with minimal overhead, allowing developers to build ABI-stable Node.js addons that are insulated from changes in the underlying JavaScript engine. Version 8.9.0 supports Node.js 18.x and newer.
What's inside node-addon-api
- node-addon-api is a collection of header-only C++ wrapper classes designed to simplify the use of the C-based Node-API when developing Node.js addons in C++. It provides a C++ object model and exception handling semantics with low overhead, making it easier to write type-safe and idiomatic C++ code for Node.js extensions.
What is Napi::ObjectReference and how does it work?
mainNapi::ObjectReferenceis a subclass ofNapi::Referencethat specifically holds aNapi::Object. It maintains a reference count for the held object; as long as this count is greater than 0, the object is protected from garbage collection.Unlike a standard
Napi::Reference, anObjectReferenceallows you to directlySetandGetproperties on the underlying object through the reference itself.#include <napi.h> using namespace Napi; void Init(Env env) { // Create an empty ObjectReference that has an initial reference count of 2. ObjectReference obj_ref = Reference<Object>::New(Object::New(env), 2); // Set a couple of different properties on the reference. obj_ref.Set("hello", String::New(env, "world")); obj_ref.Set(42, "The Answer to Life, the Universe, and Everything"); // Get the properties using the keys. Value val1 = obj_ref.Get("hello"); Value val2 = obj_ref.Get(42); }What is Napi::FunctionReference and how does it work?
mainA
Napi::FunctionReferenceis a subclass ofNapi::Referencethat acts as a wrapper for aNapi::Function. It maintains a reference count for the underlying JavaScript function.As long as the reference count is greater than 0, the function is protected from garbage collection, ensuring it remains accessible to your native add-on even if the original JavaScript reference is lost.
It provides two primary ways to execute the function:
Call: Used for synchronous execution.MakeCallback: Used for asynchronous execution (after an async operation).
Refer to
Napi::Functiondocumentation to decide which method is appropriate for your use case.What is Napi::BasicEnv and when to use it
mainTheNapi::BasicEnvobject represents an environment with a limited subset of APIs compared toNapi::Env. It is primarily used in basic finalizers where a fullNapi::Envmight not be available or safe to use. This object is typically created and passed by the Node.js runtime or thenode-addon-apiinfrastructure itself.What is node-addon-api and how does it provide ABI stability?
mainnode-addon-api is a C++ wrapper for Node-API, which is an ABI-stable C interface provided by Node.js for building native addons.
By using
node-addon-api, your native modules are insulated from changes in the underlying JavaScript engine (like V8 or ChakraCore). This allows modules compiled for one version of Node.js to run on later versions without requiring recompilation, provided they rely exclusively on the stable Node-API surface.Warning: Using other Node.js interfaces like
libuv(e.g.,#include <uv.h>) directly can break ABI stability across Node.js major versions. To maintain stability, usenode-addon-apiexclusively.How Napi::AsyncWorker works
mainOverview
Napi::AsyncWorkeris an abstract class used to move data and perform tasks between the Node.js event loop and worker threads. It handles the complexities of thread management and asynchronous execution.Lifecycle
- Creation: Subclass
Napi::AsyncWorkerand provide a callback function (which runs on the main thread). - Queueing: Call
Napi::AsyncWorker::Queue()to request execution. - Execution: When a thread is available,
Napi::AsyncWorker::Execute()is invoked on a libuv worker thread.- CRITICAL: You must NOT call any
node-addon-apimethods or any code that invokes JavaScript insideExecute(), as it is not running on the main event loop.
- CRITICAL: You must NOT call any
- Completion: Once
Execute()finishes, eitherNapi::AsyncWorker::OnOK()orNapi::AsyncWorker::OnError()is invoked on the main thread. - Destruction: After the completion methods finish, the
Napi::AsyncWorkerinstance is automatically destructed (unlessSuppressDestruct()was called).
- Creation: Subclass
Handle errors with C++ exceptions enabled
mainWhen C++ exceptions are enabled,
Napi::Errorextendsstd::exception. This allows you to use standardtry/catchblocks to manage both C++ and JavaScript exceptions.- Automatic Conversion: If a Node-API call fails (e.g., invalid arguments) or a JavaScript function called from C++ throws,
node-addon-apiautomatically converts these intoNapi::ErrorC++ exceptions. - Propagation: If an
Napi::Errorescapes a native callback, it is automatically converted into a JavaScript exception when returning to the JS environment. - Dependency Behavior:
- Using
node_addon_api_except: OnlyNapi::Errorobjects are handled. - Using
node_addon_api_except_all: All exceptions are handled.std::exceptionderivatives use theirwhat()message; others use a generic message.
- Using
// Throwing a C++ exception Env env = ... throw Napi::Error::New(env, "Example exception"); // Propagating a Node-API C++ exception (JS function call) Napi::Function jsFunctionThatThrows = someValue.As<Napi::Function>(); Napi::Value result = jsFunctionThatThrows({ arg1, arg2 }); // Handling a Node-API C++ exception try { result = jsFunctionThatThrows({ arg1, arg2 }); } catch (const Error& e) { cerr << "Caught JavaScript exception: " + e.what(); }- Automatic Conversion: If a Node-API call fails (e.g., invalid arguments) or a JavaScript function called from C++ throws,
Handle empty Napi::Value and uninitialized states
mainAn "empty"
Napi::Valueis an uninitialized instance created via the default constructorNapi::Value::Value().IsEmpty(): Returnstrueif the value is uninitialized.- Warning: An empty
Napi::Valueis invalid. Most operations on an empty value will result in an exception. - Distinction: An empty
Napi::Valueis not the same as JavaScriptnullorundefined(which are valid values). - Exception Handling: When C++ exceptions are disabled, methods returning
Napi::Valuemight return an emptyNapi::Valueto signal a pending exception. In this case, you must checkEnv::IsExceptionPendingbefore proceeding.
How Napi::AsyncProgressWorker works
mainConcept
Napi::AsyncProgressWorkeris an abstract class used to perform heavy tasks on a libuv worker thread while providing a mechanism to report progress back to the JavaScript event loop. It extendsNapi::AsyncWorkerby internally usingNapi::ThreadSafeFunctionto bridge the worker thread and the main event loop.Lifecycle
- Creation: Instantiate a subclass of
Napi::AsyncProgressWorker. - Queueing: Call
Napi::AsyncProgressWorker::Queueto request execution. - Execution: The
Executemethod runs on a background thread. During this phase, you can callExecutionProgress::Sendto report progress. - Progress Reporting:
ExecutionProgress::SendtriggersOnProgresson the main JavaScript thread. Note that multipleSendcalls might be coalesced into a singleOnProgressinvocation. - Completion: Once
Executefinishes, eitherOnOKorOnErroris invoked on the main thread. - Destruction: The instance is destructed after
OnOKorOnErrorcompletes.
Implementation Requirements
For a basic implementation, you must override:
Execute(const ExecutionProgress& progress): The background task logic.OnProgress(const T* data, size_t count): The logic to handle progress updates on the main thread.
Warning: Inside
Execute, you must not call anynode-addon-apimethods or any code that invokes JavaScript, as it runs on a thread other than the main event loop. UseOnOKorOnErrorfor JavaScript interactions after the task completes.- Creation: Instantiate a subclass of
Use Napi::SharedArrayBuffer
mainThe
Napi::SharedArrayBufferclass provides a C++ wrapper for the JavaScriptSharedArrayBufferclass. It allows you to allocate shared memory that can be accessed by multiple threads or workers.IMPORTANT: Support for
Napi::SharedArrayBufferis currently experimental. You must use theNAPI_EXPERIMENTALmacro and build against Node.js headers that support this feature.Use the Napi::TypedArray class
mainTheNapi::TypedArrayclass is a C++ wrapper for the JavaScriptTypedArrayclass. It inherits fromNapi::Object. You can use it to inspect and interact with typed arrays (likeUint8Array,Float64Array, etc.) passed from JavaScript to your Node.js addon.Iterate over Napi::Object properties
mainIf C++ exceptions are enabled (
NAPI_CPP_EXCEPTIONS), you can iterate over an object's enumerable properties using iterators. Iterators yield anstd::pairwhere.firstis theNapi::Valuekey and.secondis aNapi::Object::PropertyLValuevalue.Constant Iterator (
const_iterator)- Iterated values are immutable.
- Use for read-only traversal.
Non-Constant Iterator (
iterator)- Iterated values are mutable.
- Allows you to modify the values of properties during iteration.
Example: Summing numeric values in an object
Value Sum(const CallbackInfo& info) { Object object = info[0].As<Object>(); int64_t sum = 0; for (const auto& e : object) { sum += static_cast<Value>(e.second).As<Number>().Int64Value(); } return Number::New(info.Env(), sum); }Example: Incrementing values in an object
void Increment(const CallbackInfo& info) { Env env = info.Env(); Object object = info[0].As<Object>(); for (auto e : object) { int64_t value = static_cast<Value>(e.second).As<Number>().Int64Value(); ++value; e.second = Napi::Number::New(env, value); } }