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
PR-URL: #927
Reviewed-By: Michael Dawson <[email protected]>
  • Loading branch information
legendecas authored Aug 3, 2021
1 parent 10564a4 commit 3615041
Show file tree
Hide file tree
Showing 35 changed files with 1,391 additions and 509 deletions.
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;
```

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

0 comments on commit 3615041

Please sign in to comment.