Search code examples
node.jsnode-addon-api

Wrap native (c++) method inside a Promise


I have a synchronous C++ Node.js addon function that does an heavy operation:

Napi::Object SyncHeavyFunction(const Napi::CallbackInfo& info) {
    std::this_thread::sleep_for(std::chrono::seconds(50));
    ...
    return env.Null();
}

I'm trying to run it asynchronously wrapping it into a javascript Promise:

console.log("before creating the Promise");
let p = new Promise((resolve) => {
     const out = SyncHeavyFunction();
     reolve(out)
});
console.log("after creating the Promise");

However it seems that the creation of the Promise blocks until the underlying native function terminates. I'm wondering if this behavior is expected and which is the best way to achieve the asynchronous call of a synchronous native function only using javascript code.


Solution

  • I recently transformed gdal-next (https://github.com/yocontra/node-gdal-next) to a completely asynchronous API (https://github.com/mmomtchev/node-gdal-async).

    I will probably end writing a tutorial because it is a common problem.

    It is far from trivial, but basically it boils down to creating asynchronous workers (Napi::AsyncWorker) for each job and then invoking a JS callback on completion. It is this callback that will resolve the Promise - or once you have a function with a callback you can simply use util.promisify to return a Promise.

    You can also check https://github.com/nodejs/node-addon- api/blob/main/doc/async_worker.md for barebones example:

    #include <napi.h>
    
    #include <chrono>
    #include <thread>
    
    using namespace Napi;
    
    class EchoWorker : public AsyncWorker {
        public:
            EchoWorker(Function& callback, std::string& echo)
            : AsyncWorker(callback), echo(echo) {}
    
            ~EchoWorker() {}
        // This code will be executed on the worker thread
        void Execute() override {
            // Need to simulate cpu heavy task
            std::this_thread::sleep_for(std::chrono::seconds(1));
        }
    
        void OnOK() override {
            HandleScope scope(Env());
            Callback().Call({Env().Null(), String::New(Env(), echo)});
        }
    
        private:
            std::string echo;
    };
    
    #include <napi.h>
    
    // Include EchoWorker class
    // ..
    
    using namespace Napi;
    
    Value Echo(const CallbackInfo& info) {
        // You need to validate the arguments here.
        Function cb = info[1].As<Function>();
        std::string in = info[0].As<String>();
        EchoWorker* wk = new EchoWorker(cb, in);
        wk->Queue();
        return info.Env().Undefined();