0

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.

Luckk
  • 518
  • 3
  • 7
  • 1
    Making something a promise doesn't mean it's going to be executed in parallel. A promise constructor should only be used if you're wrapping something already asynchronous but *not* a promise. – VLAZ Jan 26 '22 at 10:41
  • If you want to simply "asynchronize" the execution you can do this: `new Promise((resolve) => setTimeout(() => resolve(SyncHeavyFunction()), 0)` – tromgy Jan 26 '22 at 12:25
  • 1
    @tromgy that's still going to block while `syncHeavyFunction()` executes. If the function takes 10 seconds, then *nothing would be able to run for 10 seconds*. The only difference is that the function execution would be scheduled for after the current event loop resolves. – VLAZ Jan 26 '22 at 14:07
  • @VLAZ, the code will continue past `new Promise` before the sync function is finished. The question was for Node, but here's a simple [example](https://stackblitz.com/edit/js-wboxsh?devtoolsheight=33&file=index.js) running in the browser. – tromgy Jan 26 '22 at 14:30
  • 1
    @tromgy as I said - after the current event loop resolves. – VLAZ Jan 26 '22 at 14:31
  • @tromgy: it block current thread after resolve, asynchronize work only when thread "free" and back to loop task. – Trương Quốc Khánh Nov 10 '22 at 14:34
  • @tromgy, learn and understand [the _event loop_](https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop). Your code does nothing more than the OP’s code. The OP’s code also _creates_ (Yes, the `Promise` instance is created first.) a `Promise` and the blocking (“stopping the event loop”) `SyncHeavyFunction` is executed _after that_. – Константин Ван Dec 10 '22 at 18:39
  • “_It seems that the creation of the `Promise` is blocked…._” **No, it isn’t blocked.** It _is created_ and after the creation of the `Promise`, the event loop goes one step further, and the task containing `SyncHeavyFunction()` is executed. It is the `SyncHeavyFunction` that is keeping the event loop from stepping (i.e. “blocking”). – Константин Ван Dec 10 '22 at 18:46

1 Answers1

1

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();
mmomtchev
  • 2,497
  • 1
  • 8
  • 23