DEV Community

Cover image for HarmonyOS Next NAPI Asynchronous Call Introduction
kouwei qing
kouwei qing

Posted on • Edited on

HarmonyOS Next NAPI Asynchronous Call Introduction

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);
Enter fullscreen mode Exit fullscreen mode

Parameters:

  • [in] env: Execution environment provided by the framework (pass directly by default).
  • [in] async_resource: Optional, associated with async_hooks.
  • [in] async_resource_name: Identifier for async resource diagnostics via async_hooks API.
  • [in] execute: Business logic function executed by the worker thread pool (non-blocking).
  • [in] complete: Callback triggered after execute 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);
Enter fullscreen mode Exit fullscreen mode

Parameters:

  • [in] env: Execution environment.
  • [in] work: Handle from napi_create_async_work().

Cancel Async Work: napi_cancel_async_work()

napi_status napi_cancel_async_work(napi_env env,
                                  napi_async_work work);
Enter fullscreen mode Exit fullscreen mode

Parameters:

  • [in] env: Execution environment.
  • [in] work: Handle from napi_create_async_work().
  • Behavior: Cancels queued work if unstarted. Returns napi_generic_failure if started. The complete callback is invoked with napi_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);
Enter fullscreen mode Exit fullscreen mode

Parameters:

  • [in] env: Execution environment.
  • [in] work: Handle from napi_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:

  1. The native method receives and converts data, stores it in context, creates an async work item, queues it, and returns nullptr (callback) or a Promise (promise).
  2. 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 after execute 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;
Enter fullscreen mode Exit fullscreen mode

Initialize Context Data

struct EncodeAudioData {  
    napi_async_work asyncWork = nullptr;  
    napi_ref callback = nullptr;  
    void* inputBuffer = nullptr;  
    size_t inputSize = 0;  
};
Enter fullscreen mode Exit fullscreen mode

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;  
}
Enter fullscreen mode Exit fullscreen mode

execute Function

static void encodeExecuteCB(napi_env env, void* data) {  
    EncodeAudioData* encodeAudioData = (EncodeAudioData*)data;  
    ope_encoder_write(pEnc, (short*)encodeAudioData->inputBuffer, encodeAudioData->inputSize / 2);  
}
Enter fullscreen mode Exit fullscreen mode

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;  
}
Enter fullscreen mode Exit fullscreen mode

eTS Call Interface

static encodePcmDataForOpusOgg(inputBuffer: ArrayBuffer): void {  
  opusOggEnc.encodePcmDataForOpusWithOgg(inputBuffer, () => {  
  });  
}
Enter fullscreen mode Exit fullscreen mode

Promise-Based Asynchronous Interface

Create Promise

napi_status napi_create_promise(napi_env env, napi_deferred* deferred, napi_value* promise);
Enter fullscreen mode Exit fullscreen mode

Initialize Context Data

struct EncodeAudioData {  
    napi_async_work asyncWork = nullptr;  
    napi_deferred deferred = nullptr;  
    void* inputBuffer = nullptr;  
    size_t inputSize = 0;  
};
Enter fullscreen mode Exit fullscreen mode

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;  
}
Enter fullscreen mode Exit fullscreen mode

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;  
}
Enter fullscreen mode Exit fullscreen mode

eTS Call Interface

static encodePcmDataForOpusOgg(inputBuffer: ArrayBuffer): void {  
  opusOggEnc.encodePcmDataForOpusWithOggPromise(inputBuffer).then(() => {  
  });  
}
Enter fullscreen mode Exit fullscreen mode

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>;
Enter fullscreen mode Exit fullscreen mode

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;  
};
Enter fullscreen mode Exit fullscreen mode

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;  
    }  
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)