node-addon-api icon indicating copy to clipboard operation
node-addon-api copied to clipboard

Emit wrapped instance to Javascript from C++ constructor

Open benoitlahoz opened this issue 2 years ago • 0 comments

Hello,

Thanks to this post, I was able to embed a simple event listener in my class.

Basically, on JS call of a static On method, my wrapped class records a callback function in a std::map.

std::map<std::string, std::vector<Napi::ThreadSafeFunction>> MyWrapper::s_listeners;

void MyWrapper::On(const Napi::CallbackInfo &info) 
{

  Napi::Env env = info.Env();

  //  Get channel name.

  std::string channel = info[0].As<Napi::String>().Utf8Value();

  // Unique callback name.
  std::string callback_name = channel + "_callback_" + std::to_string(MyWrapper::s_callbacks_count);
  MyWrapper::s_callbacks_count += 1;

  // Actual callback.

  Napi::Function fn = info[1].As<Napi::Function>();
  Napi::ThreadSafeFunction callback = Napi::ThreadSafeFunction::New(env, fn, callback_name, 0, 1); 

  // Push callback for this channel.

  MyWrapper::s_listeners[channel].push_back(callback);

}

Then, when creating a new MyWrapper instance, I call another static function to trigger the registered callbacks.

MyWrapper::MyWrapper(const Napi::CallbackInfo& info)
: Napi::ObjectWrap<MyWrapper>(info)
{


  Napi::Env env = info.Env();
  Napi::HandleScope scope(env);

  try {

    // ... omitted for brevity... 

    // Trigger eventual listeners.

    MyWrapper::_OnInstanceCreated(info);

  } catch (char const *err)
  {

    Napi::TypeError::New(env, err).ThrowAsJavaScriptException();

  }

}

void MyWrapper::_OnInstanceCreated(const Napi::CallbackInfo &info) {

  // Channel name.

  const char * channel = "created";

  // printf("IsConstructCall %i\n", info.IsConstructCall()); // Returns true.

  Napi::Function n_server = info.NewTarget().As<Napi::Function>();  // FIXME: Returns number in JS.

  // Find registered callbacks.

  auto channel_callbacks = MyWrapper::s_listeners.find(channel);

  if (channel_callbacks != MyWrapper::s_listeners.end()) {

    std::vector<Napi::ThreadSafeFunction> callbacks = channel_callbacks->second;

    for (auto it = begin(callbacks); it != end(callbacks); ++it) {

      // Calls registered callback.

      auto callback = [n_server](Napi::Env env, Napi::Function js_callback) {
        js_callback.Call({n_server});
      };

      it->NonBlockingCall(callback);

    }

  }

}

On Javascript side, I register the listener

MyAddon.MyClass.on('created', (myInstance: any) => {
  console.log(myInstance); // Returns a number of... 32759 (???)
});

Listener is correctly called, but myInstance parameter returns a number... What does this mean? Am I correctly getting the instance through info.NewTarget()? Is this that when caching the callback I'm missing something (in info[1].As<Napi::Function>())?

Basically, I try to get the newly created instance and forward it to my listener.

How can I get it? Is the wrapped object already created when I call the _OnInstanceCreated function? Is there a way to do this that I'm missing?

Any help would be highly appreciated!

Thank you!

benoitlahoz avatar Oct 12 '22 13:10 benoitlahoz