From a3bb5382ce5346e9338574c41c455816203dff7f Mon Sep 17 00:00:00 2001 From: mastergberry Date: Fri, 6 Nov 2020 10:09:58 +0100 Subject: [PATCH 1/4] 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 | 170 ++++++++++++++++++++++++++--------- 1 file changed, 130 insertions(+), 40 deletions(-) diff --git a/doc/async_worker_variants.md b/doc/async_worker_variants.md index 8b892eaee..3fe45490a 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 +`Napi::AsyncProgressQueueWorker::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(addon, 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 +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,9 +503,11 @@ 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(); } @@ -453,4 +519,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 +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 a function +nativeAddon.echo("example", onErrorCallback, onOkCallback, onProgressCallback); +``` + [`Napi::AsyncWorker`]: ./async_worker.md From 3531a6e22716244a1fe0e770f8b83b0b1d76f735 Mon Sep 17 00:00:00 2001 From: mastergberry Date: Fri, 6 Nov 2020 14:03:13 +0100 Subject: [PATCH 2/4] Resolve a few mistakes in my documentation --- doc/async_worker_variants.md | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/doc/async_worker_variants.md b/doc/async_worker_variants.md index 3fe45490a..ba629bb32 100644 --- a/doc/async_worker_variants.md +++ b/doc/async_worker_variants.md @@ -244,8 +244,8 @@ 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. If you would like to guarantee that there is one invocation of -`Napi::AsyncProgressQueueWorker::OnProgress` for every `Send` call, you should use the -`Napi::AsyncProgressQueueWorker` class instead which is documented further down this page. +`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; @@ -356,7 +356,7 @@ Object Init(Env env, Object exports) } // Register our native addon -NODE_API_MODULE(addon, Init) +NODE_API_MODULE(nativeAddon, Init) ``` The implementation of a `Napi::AsyncProgressWorker` can be used by creating a @@ -368,7 +368,7 @@ method that will queue the created worker for execution. Lastly, the following Javascript (ES6+) code would be associated the above example: ```js -nativeAddon = require('binding.node'); +const { nativeAddon } = require('binding.node'); const exampleCallback = (errorResponse, okResponse, progressData) => { // Use the data accordingly @@ -511,6 +511,17 @@ Value Echo(const CallbackInfo& info) { 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 @@ -522,7 +533,7 @@ method that will queue the created worker for execution. Lastly, the following Javascript (ES6+) code would be associated the above example: ```js -nativeAddon = require('binding.node'); +const { nativeAddon } = require('binding.node'); const onErrorCallback = (msg) => { // Use the data accordingly From 74e523e91a9a5bdd062cd00e9a2331da65c6a76d Mon Sep 17 00:00:00 2001 From: mastergberry Date: Sat, 7 Nov 2020 22:15:20 +0100 Subject: [PATCH 3/4] Small tweak to last comment since i was bad and copy/pasted it --- doc/async_worker_variants.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/async_worker_variants.md b/doc/async_worker_variants.md index ba629bb32..306591de8 100644 --- a/doc/async_worker_variants.md +++ b/doc/async_worker_variants.md @@ -550,7 +550,7 @@ const onProgressCallback = (num) => { // ... }; -// Call our native addon with the paramters of a string and a function +// Call our native addon with the paramters of a string three callback functions nativeAddon.echo("example", onErrorCallback, onOkCallback, onProgressCallback); ``` From 7fec681f03524a778f5a2c462be70f7c40c351e1 Mon Sep 17 00:00:00 2001 From: mastergberry Date: Sun, 8 Nov 2020 20:11:27 +0100 Subject: [PATCH 4/4] :( Deleted too much --- doc/async_worker_variants.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/async_worker_variants.md b/doc/async_worker_variants.md index 306591de8..54007762c 100644 --- a/doc/async_worker_variants.md +++ b/doc/async_worker_variants.md @@ -550,7 +550,7 @@ const onProgressCallback = (num) => { // ... }; -// Call our native addon with the paramters of a string three callback functions +// Call our native addon with the paramters of a string and three callback functions nativeAddon.echo("example", onErrorCallback, onOkCallback, onProgressCallback); ```