Mastering Exception Handling in Node.js Native Addons with node-addon-api

This article explains how JavaScript and C++ exceptions work in Node.js native addons, compares traditional v8::TryCatch handling with the newer Maybe-based approach in node-addon-api v4.1.0, and shows practical code examples for reliable error management.

Node Underground
Node Underground
Node Underground
Mastering Exception Handling in Node.js Native Addons with node-addon-api

Node.js Addons are essential for extending Node.js with native libraries, and the upcoming node-addon-api v4.1.0 introduces improvements to JavaScript exception handling.

JavaScript Exceptions

JavaScript uses throw to raise errors and try...catch to handle them. Exceptions can arise from ordinary functions, property getters, Proxy handlers, and even assignment statements, as demonstrated by several code snippets.

try {
  throw new Error();
} catch (error) {
  // handle error...
}
const obj = {
  get foo() {
    throw new Error();
  },
};
obj.foo; // => Error
// Proxy handler exception
const proxy = new Proxy({}, {
  ownKeys(target) {
    throw new Error();
  },
});
for (let _ in proxy) {} // => Error
// Const assignment exception
const foo = 'bar';
foo = '!!'; // => TypeError

Because any JavaScript statement may throw, addon developers must carefully handle these exceptions.

Native/C++ Exceptions

Modern C++ also provides try...catch for exception handling:

#include <stdexcept>
#include <iostream>

void fn() {
  throw std::runtime_error("bang!");
}

int main() {
  try {
    fn();
  } catch (std::runtime_error err) {
    std::cout << "Caught: " << err.what() << std::endl;
  }
}

If a C++ exception is uncaught in a Node.js addon, the process crashes because Node.js and V8 do not enable modern C++ exceptions by default.

V8 Exception Handling

V8 uses v8::TryCatch to capture JavaScript exceptions without interrupting C++ flow. Developers must check HasCaught() and retrieve the exception manually, otherwise the code may continue in an erroneous state.

void DoSomething(v8::Local<v8::Context> context, v8::Local<v8::Function> fn) {
  v8::HandleScope handle_scope(context->GetIsolate());
  v8::TryCatch try_catch(context->GetIsolate());

  fn->Call(context, fn, 0, {});

  if (try_catch.HasCaught()) {
    v8::Local<v8::Value> exception = try_catch.Exception();
    // handle exception
  }
}

V8 APIs that may throw return v8::Maybe or v8::MaybeLocal, requiring explicit checks before using the result.

node-addon-api

Before version 4.0.0, node-addon-api offered two ways to handle JavaScript exceptions:

Manually checking the engine state with env.IsExceptionPending() and clearing the exception.

Enabling C++ exceptions so that JavaScript errors are thrown as C++ exceptions, allowing standard try...catch blocks.

Both approaches require explicit handling to avoid missed errors.

Introducing Napi::Maybe

Because many projects cannot enable C++ exceptions, node-addon-api adds a Napi::Maybe type (enabled via NODE_ADDON_API_ENABLE_MAYBE) that mirrors V8’s Maybe and languages’ optional types. It represents either a valid value ( Just<T>) or the absence of a value due to an exception ( Nothing).

#define NODE_ADDON_API_ENABLE_MAYBE
#include <napi.h>
#include <iostream>

void Hello(const Napi::CallbackInfo& info) {
  Napi::Env env = info.Env();
  Napi::Object target = info[0].As<Napi::Object>();
  Napi::Maybe<Napi::String> maybe_name = target.Get("name").As<Napi::String>();
  if (maybe_name.IsNothing()) {
    Napi::Error e = env.GetAndClearPendingException();
    std::cout << "caught: " << e.Message() << std::endl;
    return;
  }
  Napi::String name = maybe_name.Unwrap();
  std::cout << "hello " << name.Utf8Value() << std::endl;
}

Using Napi::Maybe forces developers to handle all possible JavaScript exceptions at compile time, improving addon reliability. This feature will be released in node-addon-api v4.1.0.

We encourage the community to share feedback on addon development challenges.

Exception HandlingNode.jsnode-addon-apiC++Native Addon
Node Underground
Written by

Node Underground

No language is immortal—Node.js isn’t either—but thoughtful reflection is priceless. This underground community for Node.js enthusiasts was started by Taobao’s Front‑End Team (FED) to share our original insights and viewpoints from working with Node.js. Follow us. BTW, we’re hiring.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.