Как сохранить асинхронный обратный вызов для последующего использования node-addon-api / napi - PullRequest
0 голосов
/ 13 марта 2020

Я использую AsyncWorker для запуска асинхронной задачи. Проблема в том, что у меня есть множество задач, которые нужно выполнить, одну за другой, и порядок важен. Чтобы поддерживать порядок, я использую технику очередей, чтобы убедиться, что AsyncWorker объекты созданы в правильном порядке, только после завершения каждой задачи. Я сохраняю Обратный вызов в vector<Function> и передаю его в AsyncWorker, но получаю следующую ошибку:

# Fatal error in v8::HandleScope::CreateHandle()
# Cannot create a handle without a HandleScope

Есть ли какой-то другой способ сделать это? Я также попытался использовать Napi::Persistent, но я не могу передать переменную Napi::FunctionReference в AsyncWorker

Функции вызывающей стороны:

Napi::Value BlockChainWrapper::genesis(const Napi::CallbackInfo& info) {
    std::lock_guard<std::mutex> guard_ready_queue(ready_queue_mutex);
    this->ready_queue_callback.push_back(info[1].As<Napi::Function>());
    this->ready_queue_data.push_back(info[0].As<Napi::Object>());
    this->ready_queue_func.push_back(BlockChainWrapperTypes::_genesis_ready);
    this->ready_queue_env.push_back(info.Env());
    return info.Env().Undefined();
}
void BlockChainWrapper::genesis_ready() {
    AsyncBlockChainFunctions* asyncWorker = new AsyncBlockChainFunctions(this->ready_queue_callback.front(), 0, blockchain_obj, this->ready_queue_data.front());
    asyncWorker->Queue();
}

Конструктор AsyncWorker:

AsyncBlockChainFunctions::AsyncBlockChainFunctions(Napi::Function& callback, int mode, std::shared_ptr<BlockChain> _blockchain, Napi::Object& resource) : AsyncWorker(callback), mode(mode) {};

РЕДАКТИРОВАТЬ 1 Я реализовал PromiseWorker, но все еще сталкивался с этими ошибками: BlockChainWrapper наследует ObjectWrap.

Napi::Object BlockChainWrapper::Init(Napi::Env env, Napi::Object exports) {
    Napi::HandleScope scope(env);
    Napi::Function func = DefineClass(env, "BlockChainWrapper", {
        InstanceMethod("genesis", &BlockChainWrapper::genesis)
    });
    constructor = Napi::Persistent(func);
    constructor.SuppressDestruct();
    exports.Set("BlockChainWrapper", func);
    return exports;
}
# Fatal error in HandleScope::HandleScope
# Entering the V8 API without proper locking in place

Модифицированный конструктор AsyncWorker, функция класса и разрешения:

class AsyncBlockChainFunctions : public PromiseWorker

AsyncBlockChainFunctions(Napi::Promise::Deferred const &d, std::shared_ptr<BlockChain> _blockchain, int mode, Napi::Object& resource) : PromiseWorker(d), mode(mode) {}

void Resolve(Napi::Promise::Deferred const &deferred) {
            deferred.Resolve(Napi::String::New(deferred.Env(), this->block_as_json_string));
};

Функция вызывающего абонента:

Napi::Value BlockChainWrapper::genesis(const Napi::CallbackInfo& info) {
    std::lock_guard<std::mutex> guard_ready_queue(ready_queue_mutex);
    this->ready_queue_data.push_back(info[0].As<Napi::Object>());
    this->ready_queue_func.push_back(BlockChainWrapperTypes::_genesis_ready);
    this->ready_queue_env.push_back(info.Env());
    Napi::Promise::Deferred deferred = Napi::Promise::Deferred::New(info.Env());
    std::cout << "genesis" << std::endl;
    return deferred.Promise();
}

Genesis готов вызывается из другого потока управления очередями

void BlockChainWrapper::genesis_ready() {
    Napi::Env env = ready_queue_env.front();
    Napi::Promise::Deferred deferred = Napi::Promise::Deferred::New(env);
    Napi::Object input_obj = this->ready_queue_data.front().As<Napi::Object>();
    auto *x = new AsyncBlockChainFunctions(std::ref(deferred), this->blockchain_obj, 0, input_obj);
    x->Queue();
}

Ответы [ 2 ]

1 голос
/ 13 марта 2020

Я не уверен, правильно ли я вас понял или смог помочь вашей первоначальной проблеме, но вы не должны зависеть от порядка выполнения AsyncWorker. Однако ошибка, о которой вы упомянули, звучит так, как будто вы сконструировали AsyncWorker неправильно, то есть CallbackInfo может быть неисправен, то есть среда napi основана на этом.

Обещания

Однако я сильно Рекомендую использовать AsyncWorker, чтобы просто управлять временем жизни, как я задумал, и переключаться на модель обещания.

На основе https://github.com/nodejs/node-addon-api/issues/231#issuecomment -528986145

Я рекомендую использовать это в качестве базового класса:

#include <napi.h>

class PromiseWorker : public Napi::AsyncWorker {
public:
    PromiseWorker(Napi::Promise::Deferred const &d) : AsyncWorker(get_fake_callback(d.Env()).Value()), deferred(d) {}

    virtual void Resolve(Napi::Promise::Deferred const &deferred) = 0;

    void OnOK() override {
        Resolve(deferred);
    }

    void OnError(Napi::Error const &error) override {
        deferred.Reject(error.Value());
    }

private:
    static Napi::Value noop(Napi::CallbackInfo const &info) {
        return info.Env().Undefined();
    }

    Napi::Reference<Napi::Function> const &get_fake_callback(Napi::Env const &env) {
        static Napi::Reference<Napi::Function> fake_callback
                = Napi::Reference<Napi::Function>::New(Napi::Function::New(env, noop), 1);
        fake_callback.SuppressDestruct();

        return fake_callback;
    }

    Napi::Promise::Deferred deferred;
};

Вам просто тогда придется создайте подкласс, переопределите Resolve и Execute и сохраните все, что вам нужно, в рядовых ваших членов, и все готово.

Обновление : я сделал полный рабочий пример того, как чтобы использовать это обещание: https://github.com/Superlokkus/spielwiese/tree/napi_promise_example

Обратите внимание на метод обещания:

#include <napi.h>

#include "promise_worker.hpp"

struct PromiseMethodWorker : PromiseWorker {
    PromiseMethodWorker(Napi::Promise::Deferred const &d, int input)
            : PromiseWorker(d), input_{std::move(input)} {}

    void Resolve(Napi::Promise::Deferred const &deferred) override {
        deferred.Resolve(create_js(deferred.Env()));
    }

    void Execute() override {
        output_ = input_ * 5;
    }

private:
    int input_;
    int output_;

    Napi::Number create_js(Napi::Env env) const {
        Napi::Number js_value = Napi::Number::New(env, output_);
        return js_value;
    }

};

Napi::Promise PromiseMethod(const Napi::CallbackInfo &info) {
    int input =  info[0].ToNumber();

    Napi::Promise::Deferred deferred = Napi::Promise::Deferred::New(info.Env());

    auto *wk = new PromiseMethodWorker(deferred, input);
    wk->Queue();

    return deferred.Promise();
}

Использование и решение

Так что вы можете просто использовать обещание возвращается в JS:

addon.PromiseMethod(42).then(value => add.PromiseMethod(value).then(...)) 

Таким образом, вы можете легко связать эти обещания вместе, ожидая всех через Promise :: all. Но так вы избегаете так называемого ада обратного вызова . Но опять же, ваше требование рассеянного заказа звучит как XY проблема для меня.

Так что вместо многих обещаний / обратных вызовов сделайте его одним, так как AsyncWorker, кажется, не дает никакой гарантии относительно вызова приказ. Блокировка в одном обещании может остановить все. Закажите в своем родном коде.

0 голосов
/ 13 марта 2020

Следующее будет работать даже для массива

async.eachSeries(/*array*/, function(item, nextItem) {
    async.waterfall([
        function(callback) {
            //your logic
            callback(null, data);
        },
        function(data, callback) {
            //your logic
            callback(null, data2);  
        },
        function(data2, callback) {
            //your logic
            callback(null, 3); //The number is just for the argument purpose. Can be anything   
        }
        //You can continue adding as many steps as you want
    ],nextItem)         
});
...