0

I'm using AsyncWorkerto run an asynchronous task. The problem is that i have numerous tasks to be run, one after another, and the order is important. To keep order i'm using a queuing technique to make sure the AsyncWorkerobjects are created in the wright order, only once each task finishes. I'm storing the Callback in a vector<Function>, and pass that to the AsyncWorker, but i get the following error:

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

Is there some other way of going about this? I also tried using Napi::Persistent, but i can't pass a Napi::FunctionReference variable to AsyncWorker

The caller functions:

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 constructor:

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

EDIT 1 I implemented the PromiseWorker, but still ran into these errors: BlockChainWrapper inherits 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

Modified AsyncWorker constructor, class and resolve function:

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));
};

Caller function:

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 ready called from another queue management thread

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();
}
t348575
  • 674
  • 8
  • 19
  • Did you try using `async.waterfall([])` function? – Srikara B S Mar 13 '20 at 10:43
  • @SrikaraBS yep, not working – t348575 Mar 13 '20 at 10:47
  • Can you show me how you have written async waterfall? I would like to take a look – Srikara B S Mar 13 '20 at 11:01
  • @SrikaraBS ```const testAddon = require('./build/Release/blockchainNativeApi.node'); const sha2 = new testAddon.BlockChainWrapper(); const async = require('async'); async.waterfall([ function() { console.log(sha2.genesis({ difficulty: 5, prev_hash: 'de04d58dc5ccc4b9671c3627fb8d626fe4a15810bc1fe3e724feea761965fb71', data: 'testing' }, AsyncWorkerCompletion)); } ], function AsyncWorkerCompletion (err, result) { if (err) { console.log(err); } else { console.log(result); } });``` – t348575 Mar 13 '20 at 11:13
  • Looks like you missed few things. I can give you the schema of async waterfall. i'll reply to the question – Srikara B S Mar 13 '20 at 11:19
  • Given by your responses to the answers, you seem to have problem somewhere else, please post the whole code i.e. a [minimal reproducable example.](https://stackoverflow.com/help/minimal-reproducible-example) – Superlokkus Mar 13 '20 at 12:58
  • @Superlokkus The Promise technique works standalone, but the moment i place ``` auto *wk = new PromiseMethodWorker(deferred, input); wk->Queue();``` in a function inside ```ObjectWrap```, it fails with: ```# Fatal error in HandleScope::HandleScope # Entering the V8 API without proper locking in place``` – t348575 Mar 13 '20 at 14:25
  • Thats because you are not supposed to wildly replace that call. This whole PromiseWorker shebang is all about a useable pattern to avoid this class of error. Like that you are not allowed to use napi calls in the execute method but only in the resolve method, because of javascript. Thats also documented in the NAPI docs. Leave the queue call where it is, if you think you have to move it, chances are good you are doing something wrong in general, hence the reason I talk about [XY problems](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem/66378#66378) all the time – Superlokkus Mar 13 '20 at 15:39
  • @Superlokkus I don't have an napi calls in my execute. What i am trying to achieve: store information about the promise or callback in vectors, and then some time later do my computation and then resolve the promise. Since i have a queue to follow. So via JS i add some data to the queue, which when its turn arrives, is then computed and resolved. I have a detached thread checking the queue every X seconds for elements in the queue, compute's one of them, and then once the compute is done, run the next item in the queue. I need a callback for each of these compute's being completed. Suggestions – t348575 Mar 13 '20 at 15:48
  • Please post a proper question with the clear problem statement and the according code, in question form. Your question is still not a reproducable example, as I alread said, since it still lacks the code where you create the env, and more. – Superlokkus Mar 13 '20 at 16:13
  • 1
    @Superlokkus https://stackoverflow.com/questions/60674499/promise-invocation-using-node-addon-api-throws-error-or-gives-no-output – t348575 Mar 13 '20 at 17:30

2 Answers2

1

I am not sure if I understood you correctly or able to help your original problem, but you should not depend on the order of the execution of AsyncWorker. However the error you mentioned just sounds like you constructed AsyncWorker wrong, i.e. the CallbackInfo might be faulty, i.e. the napi environment its based on.

Promises

However I strongly recommend to use AsyncWorker to just handle the lifetime as I was designed to do, and to switch to the promise pattern.

Based on https://github.com/nodejs/node-addon-api/issues/231#issuecomment-528986145

I recommend you use this as a base class:

#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;
};

You just then would have to subclass it, override Resolve and Execute, and save the stuff you need in your member privates, and you are done.

Update: I made a full working example on how to use this promises: https://github.com/Superlokkus/spielwiese/tree/napi_promise_example

Note the Promise Method:

#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();
}

Usage and Solution

So you could just use the promise returned in JS:

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

So you could easily chain these promises together, of wait for all via Promise::all. But so you avoid the so called callback hell. But again, your diffuse order requirement sounds like a XY problem to me.

So instead of many promises/callbacks make it to one, since AsyncWorker seem to make no guarantee about call order. Blocking in one promise, might stall everything. Order in your native code.

Superlokkus
  • 4,731
  • 1
  • 25
  • 57
  • something like this? ```class PromiseWrapper : public PromiseWorker { public: PromiseWrapper(Napi::Promise::Deferred const &d) : PromiseWorker(d) {}; void Execute() override { std::this_thread::sleep_for(std::chrono::seconds(3)); std::cout << "slept for three seconds" << std::endl; } void Resolve(Napi::Promise::Deferred const &deferred) override { deferred.Resolve({}); } };``` – t348575 Mar 13 '20 at 11:48
  • Yes this would work, I will write an more life like example, give me 5 min. – Superlokkus Mar 13 '20 at 11:55
  • error C2440: '': cannot convert from 'initializer list' to 'T' during compilation – t348575 Mar 13 '20 at 12:24
  • Says who? On what? I compiled this with MSVC and it worked fine. I need more information, please also paste your whole code – Superlokkus Mar 13 '20 at 12:27
  • Also please note I forgot to commit my branch, so pull that git repo again, `yarn install && yarn test` works both on linux and windows with VS2019 – Superlokkus Mar 13 '20 at 12:35
-1

The following will work even for an array

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)         
});