Как проверить аддон node.js C++, если обещание разрешено

Задание:

Я вызываю обратный вызов из C++, который возвращает v8::Promise (то есть асинхронную функцию). Теперь я хочу узнать, было ли обещание разрешено.

Для этого примера здесь я хочу проверить из JS, разрешено ли обещание. Но «просто быть проинформированным в аддоне C++» было бы хорошо.

Проблема:

Мне не удалось создать постоянный объект Promise на C++. Я работаю, пока я все еще в цикле событий. Но когда я снова попадаю в цикл событий позже, объект оказывается пустым.

Код:

JS тестовый код

// create an object in the addon
var OB = require('./build/Debug/objectwraphandle.node')
var obj = new OB.MyObject(42)

// just an async wait function
async function asyncFunc1(y) {
    return new Promise((resolve, reject) => { 
        setTimeout(() => resolve('asyncFunc1: DONE'), 2000);
    });
}

// pass the async function as callback to the addon and call it
obj.callAsyncFunction(asyncFunc1);

//  ask the object, it the promise is already resolved.
// this should switch from 1 (Pending) to 0 (Resolved) after 2 seconds
console.log("  resolved? : " + obj.isPromiseResolved());
// but this core-dumps with:
// "FATAL ERROR: v8::Promise::Cast Could not convert to promise"

Теперь о C++ (кстати, нет нужды говорить, что я не гуру C++ и JS) (оригинал взят из NAN — Native Abstractions for Node.js). Для воспроизведения я помещаю здесь полный код. Важны функции CallAsyncFunction и IsPromiseResolved.

/*********************************************************************
 * NAN - Native Abstractions for Node.js
 * Copyright (c) 2018 NAN contributors
 * MIT License <https://github.com/nodejs/nan/blob/master/LICENSE.md>
 *
 * and now here:  used as test basis by Gerrit Prange
 ********************************************************************/

#include <iostream>
#include <nan.h>

using namespace Nan; 

class MyObject : public ObjectWrap {
 public:
  static NAN_MODULE_INIT(Init) {
    v8::Local<v8::FunctionTemplate> tpl = Nan::New<v8::FunctionTemplate>(New);
    tpl->SetClassName(Nan::New("MyObject").ToLocalChecked());
    tpl->InstanceTemplate()->SetInternalFieldCount(1);

    // these are the two functions in question
    SetPrototypeMethod(tpl, "callAsyncFunction", CallAsyncFunction);
    SetPrototypeMethod(tpl, "isPromiseResolved", IsPromiseResolved);

    constructor().Reset(Nan::GetFunction(tpl).ToLocalChecked());
    Set(target, Nan::New("MyObject").ToLocalChecked(),
      Nan::GetFunction(tpl).ToLocalChecked());
  }

 private:
  explicit MyObject(double value = 0) : value_(value) {}
  ~MyObject() {}

  // here: the promise is stored as persistent object
  Nan::Persistent<v8::Promise> *persistentPromise;

  static NAN_METHOD(New) {
    if (info.IsConstructCall()) {
      double value = info[0]->IsUndefined() ? 0 : Nan::To<double>(info[0]).FromJust();
      MyObject *obj = new MyObject(value);
      obj->Wrap(info.This());
      info.GetReturnValue().Set(info.This());
    } else {
      const int argc = 1;
      v8::Local<v8::Value> argv[argc] = {info[0]};
      v8::Local<v8::Function> cons = Nan::New(constructor());
      info.GetReturnValue().Set(
          Nan::NewInstance(cons, argc, argv).ToLocalChecked());
    }
  }

  /* we get a callback function (async function),
   * call this callback and get a promise returned
   */
  static NAN_METHOD(CallAsyncFunction) {
    MyObject* obj = ObjectWrap::Unwrap<MyObject>(info.Holder());

    const unsigned argc = 1;
    v8::Local<v8::Value> argv[argc] = { Nan::New("hello world").ToLocalChecked() };

    Callback cb(To<v8::Function>(info[0]).ToLocalChecked());

    // call the callback - and get a Promise
    Nan::MaybeLocal<v8::Value> promiseReturnValue = (*cb)->Call(GetCurrentContext()->Global(), argc, argv);

    // check if the promise is already resolved. (should not be in this example!)
    v8::Handle<v8::Promise> promiseReturnObject = v8::Handle<v8::Promise>::Cast ( promiseReturnValue.ToLocalChecked() );
    v8::Promise::PromiseState promiseState = promiseReturnObject->State();
    std::cout <<  " state: " << promiseState << std::endl;

    // make the callback persistent
    Nan::Persistent<v8::Promise> persistentPromiseReturnObject(promiseReturnObject);
    obj->persistentPromise = &persistentPromiseReturnObject;
  }

  /* check if the callback is already resolved and return the state
   */
  static NAN_METHOD(IsPromiseResolved) {
    MyObject* obj = ObjectWrap::Unwrap<MyObject>(info.Holder());

    v8::Local<v8::Context> context = v8::Isolate::GetCurrent()->GetCurrentContext();

    // get the persistent callback and convert it into a local object
    v8::Local<v8::Object> objectToCheckPromise = Nan::New ( *obj->persistentPromise );
    // THE LINE BELOW IS THE PROBLEM! actually, persiting does not seem to work.
    v8::Local<v8::Promise> promiseObject = v8::Local<v8::Promise>::Cast ( objectToCheckPromise );

    // get the promises state
    v8::Promise::PromiseState promiseState = promiseObject->State();

    // and return the state
    std::cout <<  " in IsPromiseResolved state: " << promiseState << std::endl;
    info.GetReturnValue().Set(Nan::New(promiseState));
  }

  static inline Persistent<v8::Function> & constructor() {
    static Persistent<v8::Function> my_constructor;
    return my_constructor;
  }

  double value_;
};

NODE_MODULE(objectwraphandle, MyObject::Init)

Фактическая ошибка, которую я получаю:

FATAL ERROR: v8::Promise::Cast Could not convert to promise
 1: node::Abort() [node]
 2: 0x121a2cc [node]
 3: v8::Utils::ReportApiFailure(char const*, char const*) [node]
 4: v8::Promise::Cast(v8::Value*) [/home/gpr/projects/own/nodejs/jsFromC_NAN_PromiseWait/build/Debug/objectwraphandle.node]
 5: v8::Local<v8::Promise> v8::Local<v8::Promise>::Cast<v8::Object>(v8::Local<v8::Object>) [/home/gpr/projects/own/nodejs/jsFromC_NAN_PromiseWait/build/Debug/objectwraphandle.node]
 6: MyObject::IsPromiseResolved(Nan::FunctionCallbackInfo<v8::Value> const&) [/home/gpr/projects/own/nodejs/jsFromC_NAN_PromiseWait/build/Debug/objectwraphandle.node]
 7: 0x7fb4dd8889dc [/home/gpr/projects/own/nodejs/jsFromC_NAN_PromiseWait/build/Debug/objectwraphandle.node]
 8: v8::internal::FunctionCallbackArguments::Call(void (*)(v8::FunctionCallbackInfo<v8::Value> const&)) [node]
 9: 0xb9043c [node]
10: v8::internal::Builtin_HandleApiCall(int, v8::internal::Object**, v8::internal::Isolate*) [node]

Извините, это длинный пост. (Но я хотел поместить код сюда — если кто-то захочет попробовать.)


person GerritP    schedule 28.06.2018    source источник


Ответы (2)


Вы можете хранить промисы в постоянных дескрипторах. Ваш код должен выглядеть примерно так:

class MyObject : public ObjectWrap {
  // ...
private:
  Nan::Persistent<Promise> persistentPromise; // not a pointer!
}

NAN_METHOD(CallAsyncFunction) {
  MyObject* obj = ObjectWrap::Unwrap<MyObject>(info.Holder());
  // ... your function call here
  // returnobj is a Local<Object>
  Local<Promise> p = Local<Promise>::Cast(returnobj);
  obj->persistentPromise.Reset(p);
}

NAN_METHOD(IsPromiseResolved) {
  MyObject* obj = ObjectWrap::Unwrap<MyObject>(info.This());
  Local<Promise> p = obj->persistentPromise.Get(Isolate::GetCurrent());
  info.GetReturnValue().Set(p->State());
}

Я не знаю, есть ли когда-либо веская причина для использования адреса или разыменования типа v8 в качестве встроенного (смотря на ваш вызов обратного вызова и на то, как вы устанавливали постоянный дескриптор). Существуют методы API, которые делают это безопасно.

Кстати, Handle устарел; Local является его заменой.

person ZachB    schedule 29.12.2018
comment
Можете ли вы помочь добавить обратный вызов, чтобы увидеть, когда обещание выполнено? Это позволило бы мне периодически проверять, было ли оно разрешено, но если я просто добавлю .Then( context, Local‹Function› .. ), я смогу получить эти обратные вызовы, когда оно разрешится. Мой текущий код выдает ошибку «TypeError: Method Promise.prototype.then вызывается для несовместимого приемника undefined» - person J Decker; 25.11.2019

Итак, похоже, v8 Promise — это не объект, который можно сделать постоянным.

Но вы можете сделать это:

Добавьте обратные вызовы разрешения и отклонения в обещание, чтобы получать информацию, когда обещание разрешено/отклонено. (Мне бы хотелось добавить «прямые обратные вызовы С++» вместо того, чтобы идти через JS, но пока это нормально.)

Или вы создаете Resolver в своем коде, который можно сделать постоянным, и вы можете разрешать или отклонять свой преобразователь позже в своем коде. (Что для меня было еще одним способом обойти мою проблему - может быть, взгляд в этом направлении также поможет вам?)

person GerritP    schedule 15.08.2018