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

src: return Maybe<> on pending exception when cpp exception disabled #927

Merged
merged 12 commits into from
Aug 3, 2021
73 changes: 69 additions & 4 deletions doc/error_handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ error-handling for C++ exceptions and JavaScript exceptions.
The following sections explain the approach for each case:

- [Handling Errors With C++ Exceptions](#exceptions)
- [Handling Errors With Maybe Type and C++ Exceptions Disabled](#noexceptions-maybe)
- [Handling Errors Without C++ Exceptions](#noexceptions)

<a name="exceptions"></a>
Expand Down Expand Up @@ -70,7 +71,7 @@ when returning to JavaScript.
### Propagating a Node-API C++ exception

```cpp
Napi::Function jsFunctionThatThrows = someObj.As<Napi::Function>();
Napi::Function jsFunctionThatThrows = someValue.As<Napi::Function>();
Napi::Value result = jsFunctionThatThrows({ arg1, arg2 });
// other C++ statements
// ...
Expand All @@ -84,7 +85,7 @@ a JavaScript exception when returning to JavaScript.
### Handling a Node-API C++ exception

```cpp
Napi::Function jsFunctionThatThrows = someObj.As<Napi::Function>();
Napi::Function jsFunctionThatThrows = someValue.As<Napi::Function>();
Napi::Value result;
try {
result = jsFunctionThatThrows({ arg1, arg2 });
Expand All @@ -96,6 +97,70 @@ try {
Since the exception was caught here, it will not be propagated as a JavaScript
exception.

<a name="noexceptions-maybe"></a>

## Handling Errors With Maybe Type and C++ Exceptions Disabled

If C++ exceptions are disabled (for more info see: [Setup](setup.md)), then the
`Napi::Error` class does not extend `std::exception`. This means that any calls to
node-addon-api functions do not throw a C++ exceptions. Instead, these node-api
functions that call into JavaScript are returning with `Maybe` boxed values.
In that case, the calling side should convert the `Maybe` boxed values with
checks to ensure that the call did succeed and therefore no exception is pending.
If the check fails, that is to say, the returning value is _empty_, the calling
side should determine what to do with `env.GetAndClearPendingException()` before
attempting to call another node-api (for more info see: [Env](env.md)).

The conversion from the `Maybe` boxed value to the actual return value is
enforced by compilers so that the exceptions must be properly handled before
continuing.

## Examples with Maybe Type and C++ exceptions disabled

### Throwing a JS exception

```cpp
Napi::Env env = ...
Napi::Error::New(env, "Example exception").ThrowAsJavaScriptException();
return;
```

After throwing a JavaScript exception, the code should generally return
immediately from the native callback, after performing any necessary cleanup.

### Propagating a Node-API JS exception

```cpp
Napi::Env env = ...
Napi::Function jsFunctionThatThrows = someValue.As<Napi::Function>();
Maybe<Napi::Value> maybeResult = jsFunctionThatThrows({ arg1, arg2 });
Napi::Value result;
if (!maybeResult.To(&result)) {
// The Maybe is empty, calling into js failed, cleaning up...
// It is recommended to return an empty Maybe if the procedure failed.
return result;
}
```

If `maybeResult.To(&result)` returns false a JavaScript exception is pending.
To let the exception propagate, the code should generally return immediately
from the native callback, after performing any necessary cleanup.

### Handling a Node-API JS exception

```cpp
Napi::Env env = ...
Napi::Function jsFunctionThatThrows = someValue.As<Napi::Function>();
Maybe<Napi::Value> maybeResult = jsFunctionThatThrows({ arg1, arg2 });
if (maybeResult.IsNothing()) {
Napi::Error e = env.GetAndClearPendingException();
cerr << "Caught JavaScript exception: " + e.Message();
}
```

Since the exception was cleared here, it will not be propagated as a JavaScript
exception after the native callback returns.

<a name="noexceptions"></a>

## Handling Errors Without C++ Exceptions
Expand Down Expand Up @@ -127,7 +192,7 @@ immediately from the native callback, after performing any necessary cleanup.

```cpp
Napi::Env env = ...
Napi::Function jsFunctionThatThrows = someObj.As<Napi::Function>();
Napi::Function jsFunctionThatThrows = someValue.As<Napi::Function>();
Napi::Value result = jsFunctionThatThrows({ arg1, arg2 });
if (env.IsExceptionPending()) {
Error e = env.GetAndClearPendingException();
Expand All @@ -143,7 +208,7 @@ the native callback, after performing any necessary cleanup.

```cpp
Napi::Env env = ...
Napi::Function jsFunctionThatThrows = someObj.As<Napi::Function>();
Napi::Function jsFunctionThatThrows = someValue.As<Napi::Function>();
Napi::Value result = jsFunctionThatThrows({ arg1, arg2 });
if (env.IsExceptionPending()) {
Napi::Error e = env.GetAndClearPendingException();
Expand Down
76 changes: 76 additions & 0 deletions doc/maybe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Maybe (template)

Class `Napi::Maybe<T>` represents a value that may be empty: every `Maybe` is
either `Just` and contains a value, or `Nothing`, and does not. `Maybe` types
are very common in node-addon-api code, as they represent that the function may
throw a JavaScript exception and cause the program to be unable to evaluate any
JavaScript code until the exception has been handled.

Typically, the value wrapped in `Napi::Maybe<T>` is [`Napi::Value`] and its
subclasses.

## Methods

### IsNothing

```cpp
template <typename T>
bool Napi::Maybe::IsNothing() const;
```

Returns `true` if the `Maybe` is `Nothing` and does not contain a value, and
`false` otherwise.

### IsJust

```cpp
template <typename T>
bool Napi::Maybe::IsJust() const;
```

Returns `true` if the `Maybe` is `Just` and contains a value, and `false`
otherwise.

### Check

```cpp
template <typename T>
void Napi::Maybe::Check() const;
```

Short-hand for `Maybe::Unwrap()`, which doesn't return a value. Could be used
where the actual value of the Maybe is not needed like `Object::Set`.
If this Maybe is nothing (empty), node-addon-api will crash the
process.

### Unwrap

```cpp
template <typename T>
T Napi::Maybe::Unwrap() const;
legendecas marked this conversation as resolved.
Show resolved Hide resolved
```

Return the value of type `T` contained in the Maybe. If this Maybe is
nothing (empty), node-addon-api will crash the process.

### UnwrapOr

```cpp
template <typename T>
T Napi::Maybe::UnwrapOr(const T& default_value) const;
```

Return the value of type T contained in the Maybe, or use a default
value if this Maybe is nothing (empty).

### UnwrapTo

```cpp
template <typename T>
bool Napi::Maybe::UnwrapTo(T* result) const;
```

Converts this Maybe to a value of type `T` in the `out`. If this Maybe is
nothing (empty), `false` is returned and `out` is left untouched.

[`Napi::Value`]: ./value.md
9 changes: 9 additions & 0 deletions doc/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,15 @@ To use **Node-API** in a native module:
```gyp
'defines': [ 'NAPI_DISABLE_CPP_EXCEPTIONS' ],
```

If you decide to use node-addon-api without C++ exceptions enabled, please
consider enabling node-addon-api safe API type guards to ensure the proper
exception handling pattern:

```gyp
'defines': [ 'NODE_ADDON_API_ENABLE_MAYBE' ],
```

4. If you would like your native addon to support OSX, please also add the
following settings in the `binding.gyp` file:

Expand Down
Loading