Skip to content

Commit

Permalink
src: return Maybe<> on pending exception when cpp exception disabled
Browse files Browse the repository at this point in the history
  • Loading branch information
legendecas committed May 23, 2021
1 parent 60348d1 commit 9684123
Show file tree
Hide file tree
Showing 32 changed files with 1,237 additions and 498 deletions.
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
functions that calling 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 calling into another node-api (for more info see: [Env](env.md)).

The conversion from `Maybe` boxed values to 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 +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
`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
JavaScript exception and cause the program unable to evaluation any JavaScript
code until the exception is 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 if the `Maybe` is `Nothing` and does not contain a value.

### 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;
```

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
value if this Maybe is nothing (empty).
### UnwrapTo
```cpp
template <typename T>
bool Napi::Maybe::UnwrapTo() 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 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

0 comments on commit 9684123

Please sign in to comment.