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_hooksAPI.
- 
[in] execute: Business logic function executed by the worker thread pool (non-blocking).
- 
[in] complete: Callback triggered afterexecutefinishes 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_okon 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_failureif started. The complete callback is invoked withnapi_cancelledon 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 afterexecutefinishes/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)