Skip to content

Commit

Permalink
Clean up AsyncProgressWorker documentation (#831)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
mastergberry authored Nov 9, 2020
1 parent c9563ca commit 1b52c28
Showing 1 changed file with 141 additions and 40 deletions.
181 changes: 141 additions & 40 deletions doc/async_worker_variants.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 <napi.h>
Expand All @@ -281,28 +284,38 @@ using namespace Napi;
class EchoWorker : public AsyncProgressWorker<uint32_t> {
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;
Expand All @@ -327,12 +340,23 @@ using namespace Napi;

Value Echo(const CallbackInfo& info) {
// We need to validate the arguments here
Function cb = info[1].As<Function>();
std::string in = info[0].As<String>();
Function cb = info[1].As<Function>();
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
Expand All @@ -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`
Expand Down Expand Up @@ -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 <napi.h>
Expand All @@ -391,31 +431,55 @@ using namespace Napi;
class EchoWorker : public AsyncProgressQueueWorker<uint32_t> {
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;
};
```

Expand All @@ -439,12 +503,25 @@ using namespace Napi;

Value Echo(const CallbackInfo& info) {
// We need to validate the arguments here.
Function cb = info[1].As<Function>();
std::string in = info[0].As<String>();
EchoWorker* wk = new EchoWorker(cb, in);
Function errorCb = info[1].As<Function>();
Function okCb = info[2].As<Function>();
Function progressCb = info[3].As<Function>();
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
Expand All @@ -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

0 comments on commit 1b52c28

Please sign in to comment.