Closed as not planned
Closed as not planned
Description
This is probably a V8 (or maybe Node.js) quirk, but since it is very node-addon-api
-centric, I am posting it here.
If a JS function called by C++ returns a rejected Promise
, there will be another phantom Promise
- most probably originating in C++ code (see the traces below, it is rejected from the microtask queue) that will trigger the unhandledRejection
check. This goes back at least to Node.js 16.
Ignoring the unhandledRejection
check - by registering a callback or using the CLI option - solves the problem and everything works as expected.
C++ code, slightly reworked TypedThreasSafeFunction
example - but the problem is not specific to it:
#include <cassert>
#include <chrono>
#include <napi.h>
#include <thread>
using namespace Napi;
using Context = Reference<Value>;
using DataType = void;
void CallJs(Napi::Env env, Function callback, Context *context, DataType *data);
using TSFN = TypedThreadSafeFunction<Context, DataType, CallJs>;
using FinalizerDataType = void;
std::thread nativeThread;
TSFN tsfn;
Value Start(const CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() < 1) {
throw TypeError::New(env, "Expected an argument");
} else if (!info[0].IsFunction()) {
throw TypeError::New(env, "Expected first arg to be function");
}
// Create a new context set to the receiver (ie, `this`) of the function call
Context *context = new Reference<Value>(Persistent(info.This()));
// Create a ThreadSafeFunction
tsfn = TSFN::New(
env,
info[0].As<Function>(), // JavaScript function called asynchronously
"Resource Name", // Name
0, // Unlimited queue
1, // Only one thread will use this initially
context,
[](Napi::Env, FinalizerDataType *,
Context *ctx) { // Finalizer used to clean threads up
nativeThread.join();
delete ctx;
});
// Create a native thread
nativeThread = std::thread([] {
// Perform a blocking call
napi_status status = tsfn.BlockingCall();
if (status != napi_ok) {
// Handle error
printf("Failed\n");
}
std::this_thread::sleep_for(std::chrono::seconds(1));
// Release the thread-safe function
tsfn.Release();
});
return env.Undefined();
}
// Transform native data into JS data, passing it to the provided
// `callback` -- the TSFN's JavaScript function.
void CallJs(Napi::Env env, Function callback, Context *context,
DataType *data) {
Napi::Value r;
// Is the JavaScript environment still available to call into, eg. the TSFN is
// not aborted
if (env != nullptr) {
// On Node-API 5+, the `callback` parameter is optional; however, this
// example does ensure a callback is provided.
if (callback != nullptr) {
r = callback.Call(context->Value(), 0, nullptr);
}
}
if (r.IsPromise()) {
printf("Resolving Promise from C++\n");
napi_value js_catch = Function::New(env, [](const CallbackInfo &info) {
printf("Caught!");
return String::New(info.Env(), "caught");
});
napi_value js_then = Function::New(env, [](const CallbackInfo &info) {
printf("Resolved!");
return String::New(info.Env(), "resolved");
});
assert(r.IsObject());
assert(r.ToObject().Get("catch").IsFunction());
r.ToObject().Get("catch").As<Function>().Call(r, 1, &js_catch);
r.ToObject().Get("then").As<Function>().Call(r, 1, &js_then);
printf("Done\n");
} else {
printf("Didn't get a Promise\n");
}
}
Napi::Object Init(Napi::Env env, Object exports) {
exports.Set("start", Function::New(env, Start));
return exports;
}
NODE_API_MODULE(clock, Init)
`binding.gyp`:
{
"targets": [
{
"target_name": "promise-from-cpp",
"includes": ["/usr/local/lib/node_modules/node-addon-api/except.gypi"],
"include_dirs": ["/usr/local/lib/node_modules/node-addon-api"],
"sources": ["promise-from-cpp.cc"]
}
]
}
JS code:
const dll = require('./build/Debug/promise-from-cpp.node');
dll.start(() => {
return Promise.reject(1337);
});
Metadata
Metadata
Assignees
Labels
No labels
Type
Projects
Status
Done