Implementing Asynchronous Tasks with Node-API Using napi_create_async_work
The Node-API functon napi_create_async_work facilitates the execution of tasks in background threads, allowing long-running operations to avoid blocking the main thread. This approach involves defining two callback functions: one for background execution and another for completion handling on the main thread.
Promise-Based Example
Define a structure to hold context data for the asynchronous operation.
struct TaskContext {
napi_async_work asyncTask = nullptr;
napi_deferred deferredPromise = nullptr;
double input = 0;
double output = 0;
};
Create the asynchronous work, queue it, and return a promise.
napi_value StartAsyncTask(napi_env env, napi_callback_info info) {
size_t paramCount = 1;
napi_value params[1];
napi_get_cb_info(env, info, ¶mCount, params, nullptr, nullptr);
auto ctx = new TaskContext();
napi_get_value_double(env, params[0], &ctx->input);
napi_create_promise(env, &ctx->deferredPromise, &promise);
napi_value taskName;
napi_create_string_utf8(env, "asyncTask", NAPI_AUTO_LENGTH, &taskName);
napi_create_async_work(env, nullptr, taskName, BackgroundTask, CompletionHandler,
ctx, &ctx->asyncTask);
napi_queue_async_work(env, ctx->asyncTask);
return promise;
}
Implement the background execution function.
static void BackgroundTask(napi_env env, void *data) {
TaskContext *ctx = reinterpret_cast<TaskContext *>(data);
ctx->output = ctx->input * 2;
}
Handle completion by resolving or rejecting the promise.
static void CompletionHandler(napi_env env, napi_status status, void *data) {
TaskContext *ctx = reinterpret_cast<TaskContext *>(data);
napi_value result;
napi_create_double(env, ctx->output, &result);
if (ctx->output > 0) {
napi_resolve_deferred(env, ctx->deferredPromise, result);
} else {
napi_reject_deferred(env, ctx->deferredPromise, result);
}
napi_delete_async_work(env, ctx->asyncTask);
delete ctx;
}
Initialize the module and call from ArkTS.
static napi_value InitializeModule(napi_env env, napi_value exports) {
napi_property_descriptor descriptor = {
"asyncTask", nullptr, StartAsyncTask, nullptr, nullptr, nullptr, napi_default, nullptr
};
napi_define_properties(env, exports, 1, &descriptor);
return exports;
}
nativeModule.asyncTask(512).then((value) => {
hilog.info(0x0000, 'ModuleTag', 'Result: %{public}d', value);
});
Callback-Based Example
Define a context structure including a callback reference.
struct CallbackContext {
napi_async_work asyncJob = nullptr;
napi_ref jsCallback = nullptr;
double operands[2] = {0};
double computationResult = 0;
};
Set up the asynchronous work with a callback parameter.
n_value AsyncOperation(napi_env env, napi_callback_info info) {
size_t argCount = 3;
napi_value arguments[3];
napi_get_cb_info(env, info, &argCount, arguments, nullptr, nullptr);
auto context = new CallbackContext();
napi_get_value_double(env, arguments[0], &context->operands[0]);
napi_get_value_double(env, arguments[1], &context->operands[1]);
napi_create_reference(env, arguments[2], 1, &context->jsCallback);
napi_value resource;
napi_create_string_utf8(env, "asyncCallback", NAPI_AUTO_LENGTH, &resource);
napi_create_async_work(env, nullptr, resource, ExecuteTask, TaskComplete,
context, &context->asyncJob);
napi_queue_async_work(env, context->asyncJob);
return nullptr;
}
Perform computation in the background.
static void ExecuteTask(napi_env env, void *data) {
CallbackContext *ctx = reinterpret_cast<CallbackContext *>(data);
ctx->computationResult = ctx->operands[0] + ctx->operands[1];
}
Invoke the JavaScript callback with the result.
static void TaskComplete(napi_env env, napi_status status, void *data) {
CallbackContext *ctx = reinterpret_cast<CallbackContext *>(data);
napi_value callbackArg[1];
napi_create_double(env, ctx->computationResult, &callbackArg[0]);
napi_value jsCallback;
napi_get_reference_value(env, ctx->jsCallback, &jsCallback);
napi_value undefined;
napi_get_undefined(env, &undefined);
napi_value callResult;
napi_call_function(env, undefined, jsCallback, 1, callbackArg, &callResult);
napi_delete_reference(env, ctx->jsCallback);
napi_delete_async_work(env, ctx->asyncJob);
delete ctx;
}
Module initialization and ArkTS invocation.
static napi_value ModuleInit(napi_env env, napi_value exports) {
napi_property_descriptor desc = {
"asyncOperation", nullptr, AsyncOperation, nullptr, nullptr, nullptr, napi_default, nullptr
};
napi_define_properties(env, exports, 1, &desc);
return exports;
}
let valueA: number = 123;
let valueB: number = 456;
nativeModule.asyncOperation(valueA, valueB, (res) => {
hilog.info(0x0000, 'ModuleTag', 'Result: %{public}d', res);
});