Search code examples
javascriptc++sciter

Pass JS function to C++ as function parameter


I want to make async call to C++ native function, but have some problems: when I call native function in async block (in Promise, exactly), it blocks UI thread and no async call is made. I want to pass callback to C++ function and call it. How can I pass function object?

class frame : public sciter::window {
public:
    frame() : window(SW_TITLEBAR | SW_RESIZEABLE | SW_CONTROLS | SW_MAIN | SW_ENABLE_DEBUG) {}

void assertion(const std::string& val)
{
    stream << std::chrono::duration_cast<std::chrono::seconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count() 
        << ' '
        << val 
        << '\n';
    stream.flush();
}

void asyncFunction(::sciter::value callback)
{
    std::thread{ [callback]() 
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(5000)); // if I use it in same thread, it blocks UI thread and no async call is made
        // call callback function
    } }.detach();
}

SOM_PASSPORT_BEGIN(frame)
    SOM_FUNCS(
        SOM_FUNC(assertion),
        SOM_FUNC(asyncFunction)
    )
SOM_PASSPORT_END

private:
    std::ofstream stream{ "log.txt" };
};

In this implementation I use another thread to make a logic. But if I want to return values (or notify, that call has completed), I need fire event (what I don't want to do, because logic will be spread on whole code) or call some sort of callback. sciter::value have methods is_function_object and is_function, so, probably, I have opportinuty to cast value to the C++ function object. But how can I do this?

<html>
    <head>
        <title>Test</title>
    </head>
    <body>
        <button #test>Assert</button>
        <script>
            function sleep(time) {
                return new Promise(resolve => {
                    Window.this.frame.asyncFunction(function (result)
                    {
                        Window.this.frame.assertion(result);
                        resolve(result);
                    }); // resolve(Window.this.frame.asyncFunction()) blocks code execution until asyncFunction returns
            });
            }

            async function answer() {
                Window.this.frame.assertion("sleep start");
                await sleep(5000);
                Window.this.frame.assertion("sleep end");
                return 50;
            }

            document.$("button#test").onclick = function () {
                var p = answer();
                p.then(result => { Window.this.frame.assertion("Ended: " + result) });
                Window.this.frame.assertion("go next");
            }
        </script>
    </body>
</html>

Solution

  • Well, here are a few possibilities: we can use callback.call() if a function or function object was passed or just return Promise like object. Examples:

    void asyncFunction(::sciter::value callback)
    {
        std::thread{ [callback]() 
        {
            std::this_thread::sleep_for(std::chrono::milliseconds(5000));
            sciter::value result = sciter::value(42);
            callback.call(result); // invoke the callback function.
        } }.detach();
    }
    

    That's how to call callback function. But we can simply return Promise like object (defined here):

     sciter::value nativeAsyncFunction(int milliseconds)
      {
        sciter::om::hasset<NativePromise> promise = new NativePromise();
    
        std::thread([=]() {
           std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds));
           promise->resolve(42);
          }).detach();
    
        return sciter::value::wrap_asset(promise);
      }
    

    In this case we don't need to create additional functions like sleep, we just call nativeAsyncFunction in answer and await its result.