From 1b52c28eb810ea46c2d5579cb16e8b6f1beb3869 Mon Sep 17 00:00:00 2001 From: mastergberry Date: Mon, 9 Nov 2020 17:57:35 +0100 Subject: [PATCH] Clean up AsyncProgressWorker documentation (#831) * Clean up AsyncProgressWorker documentation The old documentation was not very clear on how to use the API. It was missing javascript references, and was not necessarily following the best coding standards. These adjustments make it a bit more complete now by providing all three necessary parts, the worker, the hookup code, and the javascript usage of it. I have also gone ahead and rewritten the `AsyncProgressQueueWorker` example to show demonstration of the `FunctionReference` class and how to use multiple callbacks instead of one combined callback. --- doc/async_worker_variants.md | 181 +++++++++++++++++++++++++++-------- 1 file changed, 141 insertions(+), 40 deletions(-) diff --git a/doc/async_worker_variants.md b/doc/async_worker_variants.md index 8b892eaee..54007762c 100644 --- a/doc/async_worker_variants.md +++ b/doc/async_worker_variants.md @@ -243,7 +243,9 @@ be safely released. Note that `Napi::AsyncProgressWorker::ExecutionProcess::Send` merely guarantees **eventual** invocation of `Napi::AsyncProgressWorker::OnProgress`, which means multiple send might be coalesced into single invocation of `Napi::AsyncProgressWorker::OnProgress` -with latest data. +with latest data. If you would like to guarantee that there is one invocation of +`OnProgress` for every `Send` call, you should use the `Napi::AsyncProgressQueueWorker` +class instead which is documented further down this page. ```cpp void Napi::AsyncProgressWorker::ExecutionProcess::Send(const T* data, size_t count) const; @@ -269,7 +271,8 @@ function runs in the background out of the **event loop** thread and at the end the `Napi::AsyncProgressWorker::OnOK` or `Napi::AsyncProgressWorker::OnError` function will be called and are executed as part of the event loop. -The code below shows a basic example of the `Napi::AsyncProgressWorker` implementation: +The code below shows a basic example of the `Napi::AsyncProgressWorker` implementation along with an +example of how the counterpart in Javascript would appear: ```cpp #include @@ -281,28 +284,38 @@ using namespace Napi; class EchoWorker : public AsyncProgressWorker { public: - EchoWorker(Function& callback, std::string& echo) - : AsyncProgressWorker(callback), echo(echo) {} + EchoWorker(Function& okCallback, std::string& echo) + : AsyncProgressWorker(okCallback), echo(echo) {} ~EchoWorker() {} - // This code will be executed on the worker thread - void Execute(const ExecutionProgress& progress) { - // Need to simulate cpu heavy task - for (uint32_t i = 0; i < 100; ++i) { - progress.Send(&i, 1) - std::this_thread::sleep_for(std::chrono::seconds(1)); + + // This code will be executed on the worker thread + void Execute(const ExecutionProgress& progress) { + // Need to simulate cpu heavy task + // Note: This Send() call is not guaranteed to trigger an equal + // number of OnProgress calls (read documentation above for more info) + for (uint32_t i = 0; i < 100; ++i) { + progress.Send(&i, 1) + } + } + + void OnError(const Error &e) { + HandleScope scope(Env()); + // Pass error onto JS, no data for other parameters + Callback().Call({String::New(Env(), e.Message())}); } - } - void OnOK() { - HandleScope scope(Env()); - Callback().Call({Env().Null(), String::New(Env(), echo)}); - } + void OnOK() { + HandleScope scope(Env()); + // Pass no error, give back original data + Callback().Call({Env().Null(), String::New(Env(), echo)}); + } - void OnProgress(const uint32_t* data, size_t /* count */) { - HandleScope scope(Env()); - Callback().Call({Env().Null(), Env().Null(), Number::New(Env(), *data)}); - } + void OnProgress(const uint32_t* data, size_t /* count */) { + HandleScope scope(Env()); + // Pass no error, no echo data, but do pass on the progress data + Callback().Call({Env().Null(), Env().Null(), Number::New(Env(), *data)}); + } private: std::string echo; @@ -327,12 +340,23 @@ using namespace Napi; Value Echo(const CallbackInfo& info) { // We need to validate the arguments here - Function cb = info[1].As(); std::string in = info[0].As(); + Function cb = info[1].As(); EchoWorker* wk = new EchoWorker(cb, in); wk->Queue(); return info.Env().Undefined(); } + +// Register the native method for JS to access +Object Init(Env env, Object exports) +{ + exports.Set(String::New(env, "echo"), Function::New(env, Echo)); + + return exports; +} + +// Register our native addon +NODE_API_MODULE(nativeAddon, Init) ``` The implementation of a `Napi::AsyncProgressWorker` can be used by creating a @@ -341,6 +365,20 @@ asynchronous task ends and other data needed for the computation. Once created, the only other action needed is to call the `Napi::AsyncProgressWorker::Queue` method that will queue the created worker for execution. +Lastly, the following Javascript (ES6+) code would be associated the above example: + +```js +const { nativeAddon } = require('binding.node'); + +const exampleCallback = (errorResponse, okResponse, progressData) => { + // Use the data accordingly + // ... +}; + +// Call our native addon with the paramters of a string and a function +nativeAddon.echo("example", exampleCallback); +``` + # AsyncProgressQueueWorker `Napi::AsyncProgressQueueWorker` acts exactly like `Napi::AsyncProgressWorker` @@ -379,7 +417,9 @@ void Napi::AsyncProgressQueueWorker::ExecutionProcess::Send(const T* data, size_ ## Example -The code below shows a basic example of the `Napi::AsyncProgressQueueWorker` implementation: +The code below show an example of the `Napi::AsyncProgressQueueWorker` implementation, but +also demonsrates how to use multiple `Napi::Function`'s if you wish to provide multiple +callback functions for more object oriented code: ```cpp #include @@ -391,31 +431,55 @@ using namespace Napi; class EchoWorker : public AsyncProgressQueueWorker { public: - EchoWorker(Function& callback, std::string& echo) - : AsyncProgressQueueWorker(callback), echo(echo) {} + EchoWorker(Function& okCallback, Function& errorCallback, Function& progressCallback, std::string& echo) + : AsyncProgressQueueWorker(okCallback), echo(echo) { + // Set our function references to use them below + this->errorCallback.Reset(errorCallback, 1); + this->progressCallback.Reset(progressCallback, 1); + } ~EchoWorker() {} - // This code will be executed on the worker thread - void Execute(const ExecutionProgress& progress) { - // Need to simulate cpu heavy task - for (uint32_t i = 0; i < 100; ++i) { - progress.Send(&i, 1); - std::this_thread::sleep_for(std::chrono::seconds(1)); + + // This code will be executed on the worker thread + void Execute(const ExecutionProgress& progress) { + // Need to simulate cpu heavy task to demonstrate that + // every call to Send() will trigger an OnProgress function call + for (uint32_t i = 0; i < 100; ++i) { + progress.Send(&i, 1); + } } - } - void OnOK() { - HandleScope scope(Env()); - Callback().Call({Env().Null(), String::New(Env(), echo)}); - } + void OnOK() { + HandleScope scope(Env()); + // Call our onOkCallback in javascript with the data we were given originally + Callback().Call({String::New(Env(), echo)}); + } + + void OnError(const Error &e) { + HandleScope scope(Env()); + + // We call our callback provided in the constructor with 2 parameters + if (!this->errorCallback.IsEmpty()) { + // Call our onErrorCallback in javascript with the error message + this->errorCallback.Call(Receiver().Value(), {String::New(Env(), e.Message())}); + } + } - void OnProgress(const uint32_t* data, size_t /* count */) { - HandleScope scope(Env()); - Callback().Call({Env().Null(), Env().Null(), Number::New(Env(), *data)}); - } + void OnProgress(const uint32_t* data, size_t /* count */) { + HandleScope scope(Env()); + + if (!this->progressCallback.IsEmpty()) { + // Call our onProgressCallback in javascript with each integer from 0 to 99 (inclusive) + // as this function is triggered from the above Send() calls + this->progressCallback.Call(Receiver().Value(), {Number::New(Env(), *data)}); + } + } private: std::string echo; + FunctionReference progressCallback; + FunctionReference errorCallback; + }; ``` @@ -439,12 +503,25 @@ using namespace Napi; Value Echo(const CallbackInfo& info) { // We need to validate the arguments here. - Function cb = info[1].As(); std::string in = info[0].As(); - EchoWorker* wk = new EchoWorker(cb, in); + Function errorCb = info[1].As(); + Function okCb = info[2].As(); + Function progressCb = info[3].As(); + EchoWorker* wk = new EchoWorker(okCb, errorCb, progressCb, in); wk->Queue(); return info.Env().Undefined(); } + +// Register the native method for JS to access +Object Init(Env env, Object exports) +{ + exports.Set(String::New(env, "echo"), Function::New(env, Echo)); + + return exports; +} + +// Register our native addon +NODE_API_MODULE(nativeAddon, Init) ``` The implementation of a `Napi::AsyncProgressQueueWorker` can be used by creating a @@ -453,4 +530,28 @@ asynchronous task ends and other data needed for the computation. Once created, the only other action needed is to call the `Napi::AsyncProgressQueueWorker::Queue` method that will queue the created worker for execution. +Lastly, the following Javascript (ES6+) code would be associated the above example: + +```js +const { nativeAddon } = require('binding.node'); + +const onErrorCallback = (msg) => { + // Use the data accordingly + // ... +}; + +const onOkCallback = (echo) => { + // Use the data accordingly + // ... +}; + +const onProgressCallback = (num) => { + // Use the data accordingly + // ... +}; + +// Call our native addon with the paramters of a string and three callback functions +nativeAddon.echo("example", onErrorCallback, onOkCallback, onProgressCallback); +``` + [`Napi::AsyncWorker`]: ./async_worker.md