HarmonyOS Next NAPI Asynchronous Call Introduction
Why Asynchronous Calls Are Needed?
TS is single-threaded. If a TS-invoked C++ method performs time-consuming tasks (e.g., file operations, network requests, database operations, image processing), these tasks should be executed asynchronously via threads in the C++ layer. Asynchronous task results are typically obtained through callbacks. Maintaining synchronization between native and main threads requires effort, but NAPI (based on the asynchronous I/O library libuv) provides an asynchronous call mechanism to manage threads uniformly, simplifying development.
Introduction to NAPI Asynchronous Calls
HarmonyOS Next supports callback and promise-based asynchronous interfaces following the OpenHarmony Napi standard.
Introduction to Asynchronous Call APIs
Create Async Work: napi_create_async_work()
napi_status napi_create_async_work(napi_env env,
napi_value async_resource,
napi_value async_resource_name,
napi_async_execute_callback execute,
napi_async_complete_callback complete,
void* data,
napi_async_work* result);
Parameters:
-
[in] env
: Execution environment provided by the framework (pass directly by default). -
[in] async_resource
: Optional, associated withasync_hooks
. -
[in] async_resource_name
: Identifier for async resource diagnostics viaasync_hooks
API. -
[in] execute
: Business logic function executed by the worker thread pool (non-blocking). -
[in] complete
: Callback triggered afterexecute
finishes or is canceled (runs in the EventLoop thread). -
[in] data
: User-provided context data for passing parameters. -
[out] result
: Pointer to the created async work item. -
Return:
napi_ok
on success, otherwise an error code.
Queue Async Work: napi_queue_async_work()
napi_status napi_queue_async_work(napi_env env,
napi_async_work work);
Parameters:
-
[in] env
: Execution environment. -
[in] work
: Handle fromnapi_create_async_work()
.
Cancel Async Work: napi_cancel_async_work()
napi_status napi_cancel_async_work(napi_env env,
napi_async_work work);
Parameters:
-
[in] env
: Execution environment. -
[in] work
: Handle fromnapi_create_async_work()
. -
Behavior: Cancels queued work if unstarted. Returns
napi_generic_failure
if started. The complete callback is invoked withnapi_cancelled
on success. Do not delete the work before the callback executes.
Delete Async Work: napi_delete_async_work()
napi_status napi_delete_async_work(napi_env env,
napi_async_work work);
Parameters:
-
[in] env
: Execution environment. -
[in] work
: Handle fromnapi_create_async_work()
.
Principle of Asynchronous Implementation
In synchronous mode, all code runs in the native method (main thread). Asynchronous mode uses napi_create_async_work()
to create an async work item:
- The native method receives and converts data, stores it in context, creates an async work item, queues it, and returns
nullptr
(callback) or aPromise
(promise). - The async work item defines two functions:
-
execute
: Runs in the worker thread (non-blocking), processes business logic, and writes results to context. -
complete
: Runs in the EventLoop thread afterexecute
finishes/cancels, converts results to JS types, and invokes callbacks or resolves promises.
-
Asynchronous processing flow chart:
Advantages Over Native Threads
-
Thread Management: NodeJS abstracts thread lifecycle and synchronization.
napi_create_async_work()
executes tasks in the background and calls back to the JS thread automatically via libuv's event loop. - Memory Management: NodeJS garbage collection integrates with async work. N-API manages resource lifecycles to prevent premature recycling.
- Callback Mechanism: Provides a seamless callback mechanism for JS-C++ interaction, integrating with the JS event loop.
- Thread Pool & Concurrency: Leverages NodeJS's libuv thread pool, eliminating manual thread management.
- Error Handling: Simplifies error propagation from native code to JS via callback exceptions.
Asynchronous Interface Implementation
Callback-Based Asynchronous Interface (Audio Encoding Example)
eTS Definition of the Example Interface
export const encodePcmDataForOpusWithOgg: (inputBuffer: ArrayBuffer, callback: () => void) => void;
Initialize Context Data
struct EncodeAudioData {
napi_async_work asyncWork = nullptr;
napi_ref callback = nullptr;
void* inputBuffer = nullptr;
size_t inputSize = 0;
};
Create Async Work Item
static napi_value encodePCMToOpusOggNative(napi_env env, napi_callback_info info) {
size_t argc = 2;
napi_value args[2] = {nullptr};
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
auto audioData = new EncodeAudioData{.asyncWork = nullptr};
napi_get_arraybuffer_info(env, args[0], &audioData->inputBuffer, &audioData->inputSize);
napi_create_reference(env, args[1], 1, &audioData->callback);
napi_value resourceName = nullptr;
napi_create_string_utf8(env, "encodePCMToOpusOggNative", NAPI_AUTO_LENGTH, &resourceName);
napi_create_async_work(env, nullptr, resourceName, encodeExecuteCB, encodeAsyncCompleteCB, (void*)audioData, &audioData->asyncWork);
napi_queue_async_work(env, audioData->asyncWork);
return nullptr;
}
execute
Function
static void encodeExecuteCB(napi_env env, void* data) {
EncodeAudioData* encodeAudioData = (EncodeAudioData*)data;
ope_encoder_write(pEnc, (short*)encodeAudioData->inputBuffer, encodeAudioData->inputSize / 2);
}
complete
Function
static void encodeAsyncCompleteCB(napi_env env, napi_status status, void* data) {
EncodeAudioData* encodeAudioData = (EncodeAudioData*)data;
napi_value callback = nullptr;
napi_value undefined = nullptr;
napi_get_undefined(env, &undefined);
napi_get_reference_value(env, encodeAudioData->callback, &callback);
napi_call_function(env, undefined, callback, 0, nullptr, &callbackResult);
if (encodeAudioData->callback != nullptr) {
napi_delete_reference(env, encodeAudioData->callback);
}
napi_delete_async_work(env, encodeAudioData->asyncWork);
delete encodeAudioData;
}
eTS Call Interface
static encodePcmDataForOpusOgg(inputBuffer: ArrayBuffer): void {
opusOggEnc.encodePcmDataForOpusWithOgg(inputBuffer, () => {
});
}
Promise-Based Asynchronous Interface
Create Promise
napi_status napi_create_promise(napi_env env, napi_deferred* deferred, napi_value* promise);
Initialize Context Data
struct EncodeAudioData {
napi_async_work asyncWork = nullptr;
napi_deferred deferred = nullptr;
void* inputBuffer = nullptr;
size_t inputSize = 0;
};
Create Async Work Item
static napi_value encodePromise(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value args[1] = {nullptr};
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
napi_value promise = nullptr;
napi_deferred deferred = nullptr;
napi_create_promise(env, &deferred, &promise);
auto audioData = new EncodeAudioData{.asyncWork = nullptr, .deferred = deferred};
napi_get_arraybuffer_info(env, args[0], &audioData->inputBuffer, &audioData->inputSize);
napi_value resourceName = nullptr;
napi_create_string_utf8(env, "encodeAudioAsyncCallback", NAPI_AUTO_LENGTH, &resourceName);
napi_create_async_work(env, nullptr, resourceName, encodeExecuteCB, encodePromiseCompleteCB, (void*)audioData, &audioData->asyncWork);
napi_queue_async_work(env, audioData->asyncWork);
return promise;
}
complete
Callback
static void encodePromiseCompleteCB(napi_env env, napi_status status, void* data) {
EncodeAudioData* encodeAudioData = (EncodeAudioData*)data;
napi_value undefined;
napi_get_undefined(env, &undefined);
napi_resolve_deferred(env, encodeAudioData->deferred, undefined);
if (encodeAudioData->callback != nullptr) {
napi_delete_reference(env, encodeAudioData->callback);
}
napi_delete_async_work(env, encodeAudioData->asyncWork);
delete encodeAudioData;
}
eTS Call Interface
static encodePcmDataForOpusOgg(inputBuffer: ArrayBuffer): void {
opusOggEnc.encodePcmDataForOpusWithOggPromise(inputBuffer).then(() => {
});
}
Standardized Asynchronous Interface
If Promise support is enabled, async methods must support both callback and promise modes. Determine the mode by the number of parameters:
export function encodePcmDataForOpusWithOgg(inputBuffer: ArrayBuffer, callback: () => void): void;
export function encodePcmDataForOpusWithOgg(inputBuffer: ArrayBuffer): Promise<void>;
Unified Context Structure
struct EncodeAudioData {
napi_async_work asyncWork = nullptr;
napi_ref callback = nullptr;
napi_deferred deferred = nullptr;
void* inputBuffer = nullptr;
size_t inputSize = 0;
};
Native Method Implementation
static napi_value encodePCMToOpusOggNative(napi_env env, napi_callback_info info) {
size_t argc = 2;
napi_value args[2] = {nullptr};
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
auto audioData = new EncodeAudioData{.asyncWork = nullptr};
if (argc == 1) {
napi_value promise = nullptr;
napi_deferred deferred = nullptr;
napi_create_promise(env, &deferred, &promise);
audioData->deferred = deferred;
napi_get_arraybuffer_info(env, args[0], &audioData->inputBuffer, &audioData->inputSize);
napi_value resourceName = nullptr;
napi_create_string_utf8(env, "encodePCMToOpusOggNativePromise", NAPI_AUTO_LENGTH, &resourceName);
napi_create_async_work(env, nullptr, resourceName, encodeExecuteCB, encodePromiseCompleteCB, (void*)audioData, &audioData->asyncWork);
napi_queue_async_work(env, audioData->asyncWork);
return promise;
} else {
napi_get_arraybuffer_info(env, args[0], &audioData->inputBuffer, &audioData->inputSize);
napi_create_reference(env, args[1], 1, &audioData->callback);
napi_value resourceName = nullptr;
napi_create_string_utf8(env, "encodePCMToOpusOggNativeCallback", NAPI_AUTO_LENGTH, &resourceName);
napi_create_async_work(env, nullptr, resourceName, encodeExecuteCB, encodeAsyncCompleteCB, (void*)audioData, &audioData->asyncWork);
napi_queue_async_work(env, audioData->asyncWork);
return nullptr;
}
}
Top comments (0)