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
72 changes: 68 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,69 @@ 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 function do not throw a C++ exceptions. Instead, these node-api
legendecas marked this conversation as resolved.
Show resolved Hide resolved
functions that calling into JavaScript are returning with `Maybe` boxed values.
legendecas marked this conversation as resolved.
Show resolved Hide resolved
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 calling into another node-api (for more info see: [Env](env.md)).
legendecas marked this conversation as resolved.
Show resolved Hide resolved

The conversion from `Maybe` boxed values to actual return value is enforced by
legendecas marked this conversation as resolved.
Show resolved Hide resolved
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 +191,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 +207,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
74 changes: 74 additions & 0 deletions doc/maybe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Maybe (template)

Class `Napi::Maybe<T>` represents an maybe empty value: every `Maybe` is either
legendecas marked this conversation as resolved.
Show resolved Hide resolved
`Just` and contains a value, or `Nothing`, and does not. `Maybe` types are very
common in node-addon-api code, as they represent the function may throw a
legendecas marked this conversation as resolved.
Show resolved Hide resolved
JavaScript exception and cause the program unable to evaluation any JavaScript
legendecas marked this conversation as resolved.
Show resolved Hide resolved
code until the exception is been handled.
legendecas marked this conversation as resolved.
Show resolved Hide resolved

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 if the `Maybe` is `Nothing` and does not contain a value.
legendecas marked this conversation as resolved.
Show resolved Hide resolved

### IsJust

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

Returns if the `Maybe` is `Just` and contains a value.

### 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 using a default
legendecas marked this conversation as resolved.
Show resolved Hide resolved
value if this Maybe is nothing (empty).

### UnwrapTo

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

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.
legendecas marked this conversation as resolved.
Show resolved Hide resolved

[`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 proper
legendecas marked this conversation as resolved.
Show resolved Hide resolved
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