Implementing Asynchronous Operations in OpenHarmony NAPI with Callbacks and Promises

This article builds upon the foundation of porting third-party libraries to OpenHarmony NAPI. We'll modify the hellonapi.cpp and index.ets files to explore asynchronous operations using Promises and Callbacks within the NAPI framework. This guide includes three examples: a Callback-based asynchronous interface, a Promise-based asynchronous interface, and a unified asynchronous interface. The source code for these examples is provided at the end of this article for your convenience.

The development environment is based on OpenHarmony version 3.2 Beta 3 with API 9. The target development board is the RONGLE DAYU200.

Understanding NAPI Asynchronous Implementation

Synchronous vs. Asynchronous:

  • Synchronous: All code execution occurs within the native method (main thread).
  • Asynchronous: Code execution is distributed across multiple threads.

Steps for Implementing NAPI Asynchronous Methods:

  1. Immediately return a preliminary result to the JavaScript caller.
  2. Initiate a separate thread to handle the asynchronous business logic.
  3. Return the final result via a callback or a Promise.

Asynchronous Work Item Workflow:

When a native method is invoked:

  1. The native method handles data reception, type conversion, and context data storage.
  2. An asynchronous work item is created.
  3. This work item is added to a queue and managed by the asynchronous thread pool.
  4. The native method then returns either a null value (for Callback) or a Promise object (for Promise).

Asynchronous operations rely on the napi_create_async_work() function provided by the NAPI framework to create asynchronous work items. This function is defined in foundation/arkui/napi/native_engine/native_node_api.cpp.

NAPI_EXTERN 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);

Parameter Descriptions:

  • env: The JavaScript execution environment provided by the framework.
  • async_resource: Optional, used for associating with async_hooks.
  • async_resource_name: An identifier for the asynchronous resource, primarily used for diagnostic information exposure via async_hooks.
  • execute: The callback function that performs the business logic. This function is executed by a worker thread, making it suitable for I/O or CPU-intensive tasks without blocking the main thread.
  • complete: The callback function executed after the execute function completes or is canceled. This function runs on the EventLoop thread.
  • data: User-provided context data passed between callbacks.
  • result: A pointer to a napi_async_work structure that will hold the created asynchronous work item. Returns napi_ok on success.

The napi_create_async_work() function involves two key callbacks:

  • execute: This function executes the core business logic. It retrieves input data from the context, performs computations (e.g., I/O, heavy calculations) in a worker thread without blocking the main thread, and stores the results in the context data. Importantly, execute functions cannot call NAPI interfaces directly as they do not run on the JS thread.
  • complete: This function is triggered after execute finishes or is canceled. It runs on the JS thread (EventLoop). It retrieves the results from the context, converts them to JavaScript types, and then either invokes the JavaScript callback function or resolves a Promise. NAPI interfaces can be called within complete to package results into JavaScript objects.

Additional functions for managing asynchronous operations include:

  • napi_delete_async_work(napi_env env, napi_async_work work): Deletes an asynchronous work item.
  • napi_queue_async_work(napi_env env, napi_async_work work): Adds a created asynchronous work item to the queue for execution.
  • napi_cancel_async_work(napi_env env, napi_async_work work): Cancels an asynchronous work item.

NAPI Asynchronous Models: Promise and Callback

OpenHarmony's NAPI supports both Promise and Callback asynchronous models. The standard system requires that if Promise support is enabled, asynchronous methods must also support the Callback pattern. Developers can choose their preferred method. The absence of a callback function argument indicates the Promise pattern, while its presence signifies the Callback pattern.

Both Promise and Callback are part of the OHOS standard asynchronous model.

Callback Asynchronous Model

When a user invokes an interface that uses the Callback model, the interface implementation performs the task asynchronously. The result is then passed as an argument to a user-registered callback function. The first argument to this callback is typically an Error object or undefined, indicating success or failure.

Promise Asynchronous Model

A Promise represents the eventual result of an asynchronous operation. It provides a cleaner way to handle asynchronous code compared to traditional callbacks, avoiding nested callbacks ("callback hell"). A Promise object has a state that, once changed, remains immutable. It can be in one of three states: pending, fulfilled, or rejected.

ES6 introduced the native Promise object as a solution for asynchronous programming, offering a more structured approach than callbacks and events. When an asynchronous task is initiated, it returns a Promise object. The results of the asynchronous operation are then accessible through the Promise's methods (e.g., .then(), .catch()).

ES6 (ECMAScript 6.0) is the international standard for the JavaScript language, with JavaScript being its primary implementation.

Callback Asynchronous Interface Example

hellonapi.cpp (Callback Example)

#include <string.h>
#include <stdio.h>
#include "napi/native_node_api.h"
#include "napi/native_api.h"

// Structure to hold context data passed between threads
struct AddonData {
  napi_async_work asyncWork = nullptr;
  napi_deferred deferred = nullptr; // Not used in Callback, but kept for structure consistency
  napi_ref callback = nullptr;
  double args[2] = {0};
  double result = 0;
};

// Native method for executing the asynchronous task (e.g., calculation)
static void addExecuteCB(napi_env env, void *data) {
  AddonData *addonData = static_cast<addondata>(data);
  // Perform the actual computation in a worker thread
  addonData->result = addonData->args[0] + addonData->args[1];
}

// Callback function executed after the asynchronous task is complete
static void addCallbackCompleteCB(napi_env env, napi_status status, void *data) {
  AddonData *addonData = static_cast<addondata>(data);
  napi_value callback = nullptr;
  napi_get_reference_value(env, addonData->callback, &callback); // Retrieve the JS callback

  napi_value undefined = nullptr;
  napi_get_undefined(env, &undefined);

  napi_value result = nullptr;
  napi_create_double(env, addonData->result, &result); // Convert C++ result to napi_value

  napi_value callbackResult = nullptr;
  napi_call_function(env, undefined, callback, 1, &result, &callbackResult); // Invoke the JS callback

  // Clean up the napi_ref
  if (addonData->callback != nullptr) {
    napi_delete_reference(env, addonData->callback);
  }

  // Clean up the async work item
  napi_delete_async_work(env, addonData->asyncWork);
  delete addonData; // Free the allocated context data
}

// Main native function exposed to JavaScript for initiating the async operation
static napi_value addCallback(napi_env env, napi_callback_info info) {
  size_t argc = 3; // Expecting two numbers and one callback function
  napi_value args[3];
  napi_value thisArg = nullptr;
  NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, &thisArg, nullptr));

  // Validate argument types
  napi_valuetype valuetype0;
  NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0));
  napi_valuetype valuetype1;
  NAPI_CALL(env, napi_typeof(env, args[1], &valuetype1));
  if (valuetype0 != napi_number || valuetype1 != napi_number) {
    napi_throw_type_error(env, nullptr, "Wrong arguments. 2 numbers expected.");
    return nullptr;
  }

  napi_valuetype valuetype2;
  NAPI_CALL(env, napi_typeof(env, args[2], &valuetype2));
  if (valuetype2 != napi_function) {
    napi_throw_type_error(env, nullptr, "Callback function expected.");
    return nullptr;
  }

  // Allocate context data for the async operation
  auto addonData = new AddonData();

  // Store arguments and the callback reference
  NAPI_CALL(env, napi_get_value_double(env, args[0], &addonData->args[0]));
  NAPI_CALL(env, napi_get_value_double(env, args[1], &addonData->args[1]));
  NAPI_CALL(env, napi_create_reference(env, args[2], 1, &addonData->callback)); // Create a persistent reference

  // Create the asynchronous work item
  napi_value resourceName = nullptr;
  napi_create_string_utf8(env, "addCallback", NAPI_AUTO_LENGTH, &resourceName);
  NAPI_CALL(env, napi_create_async_work(env, nullptr, resourceName,
                                         addExecuteCB, addCallbackCompleteCB,
                                         static_cast<void>(addonData),
                                         &addonData->asyncWork));

  // Queue the asynchronous work item for execution
  NAPI_CALL(env, napi_queue_async_work(env, addonData->asyncWork));

  // Return null as the initial value for callback-based async operations
  napi_value result = nullptr;
  NAPI_CALL(env, napi_get_null(env, &result));
  return result;
}

// Function to register NAPI properties (functions, properties)
static napi_value registerFunc(napi_env env, napi_value exports) {
  napi_property_descriptor desc[] = {
      DECLARE_NAPI_FUNCTION("addCallback", addCallback),
  };
  NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc));
  return exports;
}

// Module definition structure
static napi_module hellonapiModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = registerFunc,
    .nm_modname = "hellonapi", // Module name, used in JS import statements
    .nm_priv = nullptr,
    .reserved = {0},
};

// Module registration function (called on module load)
extern "C" __attribute__((constructor)) void hellonapiModuleRegister() {
  napi_module_register(&hellonapiModule);
}
</void></addondata></addondata>

index.ets (Callback Example)

import prompt from '@system.prompt';
import hellonapi from '@ohos.hellonapi';

@Entry
@Component
struct TestAdd {
  build() {
    Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
      Button("hellonapi.addCallback(x, y, callback)")
        .margin(10)
        .fontSize(20)
        .onClick(() => {
          const num1 = 123;
          const num2 = 456;
          hellonapi.addCallback(num1, num2, (result) => {
            prompt.showToast({ message: `hellonapi.addCallback(${num1}, ${num2}) = ${result}` });
          });
        });
    }
    .width('100%')
    .height('100%');
  }
}

@ohos.hellonapi.d.ts (Callback Example)

declare namespace hellonapi {
    function addCallback(num1: number, num2: number, callback: (result: number) => void): void;
    /**
     * @since 9
     * @syscap SystemCapability.Ability.AbilityRuntime.AbilityCore
     */
}
export default hellonapi;

Main Thread: Parameter Handling

The main thread receives and validates parameters passed from JavaScript using napi_typeof() to check their types. If the types are incorrect, a napi_throw_type_error() is issued.

Main Thread: Context Initialization

A structure (AddonData) is defined to hold data that needs to be shared between the main thread, worker thread, and EventLoop thread. This structure includes the asynchronous work item handle, a reference to the callback function, input arguments, and the computation result. The JavaScript callback function (a napi_value) is converted into a persistent reference (napi_ref) using napi_create_reference(). This is crucial because napi_value references are temporary and may become invalid after the native method returns. napi_ref ensures the callback remains accessible throughout the asynchronous operation.

Main Thread: Creating the Asynchronous Work Item

The napi_create_async_work() function is used to create the asynchronous work item. This function takes the execute and complete callback functions, along with the context data, and returns a handle to the work item. This handle is stored in the AddonData structure.

Main Thread: Queuing the Asynchronous Work Item

After creation, the asynchronous work item is added to the execution queue using napi_queue_async_work(). This allows the NAPI framework to schedule and execute the work item on a separate thread.

Main Thread: Returning Temporary Value

For callback-based asynchronous operations, the native method immediately returns null (or an empty object) using napi_get_null(). This signals to JavaScript that the operation has started but the final result is not yet available.

Worker Thread: Executing Business Logic

The addExecuteCB function, running in a worker thread, performs the actual computation. It accesses the input arguments from the context data (addonData->args) and stores the result in addonData->result. This process does not block the main JavaScript thread.

EventLoop Thread: Completing the Operation

The addCallbackCompleteCB function executes on the EventLoop thread. It retrieves the computed result from the context data, converts it back to a JavaScript napi_value using napi_create_double(), and then invokes the JavaScript callback function using napi_call_function(). Finally, it cleans up resources by deleting the napi_ref and the asynchronous work item, and deallocating the context data.

Promise Asynchronous Interface Example

hellonapi.cpp (Promise Example)

#include <string.h>
#include <stdio.h>
#include "napi/native_node_api.h"
#include "napi/native_api.h"

// Structure to hold context data passed between threads
struct AddonData {
  napi_async_work asyncWork = nullptr;
  napi_deferred deferred = nullptr; // Deferred object for Promise
  napi_ref callback = nullptr;      // Not used in Promise, but kept for structure consistency
  double args[2] = {0};
  double result = 0;
};

// Native method for executing the asynchronous task
static void addExecuteCB(napi_env env, void *data) {
  AddonData *addonData = static_cast<addondata>(data);
  addonData->result = addonData->args[0] + addonData->args[1];
}

// Callback function executed after the asynchronous task is complete for Promise
static void addPromiseCompleteCB(napi_env env, napi_status status, void *data) {
  AddonData *addonData = static_cast<addondata>(data);
  napi_value result = nullptr;
  napi_create_double(env, addonData->result, &result); // Convert C++ result to napi_value

  // Resolve the Promise with the result
  napi_resolve_deferred(env, addonData->deferred, result);

  // Clean up the napi_ref (if any, though not typically used here)
  if (addonData->callback != nullptr) {
    napi_delete_reference(env, addonData->callback);
  }

  // Clean up the async work item
  napi_delete_async_work(env, addonData->asyncWork);
  delete addonData; // Free the allocated context data
  addonData = nullptr;
}

// Main native function exposed to JavaScript for initiating the Promise-based async operation
static napi_value addPromise(napi_env env, napi_callback_info info) {
  size_t argc = 2; // Expecting two numbers
  napi_value args[2];
  napi_value thisArg = nullptr;
  NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, &thisArg, nullptr));

  // Validate argument types
  napi_valuetype valuetype0;
  NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0));
  napi_valuetype valuetype1;
  NAPI_CALL(env, napi_typeof(env, args[1], &valuetype1));
  if (valuetype0 != napi_number || valuetype1 != napi_number) {
    napi_throw_type_error(env, nullptr, "Wrong arguments. 2 numbers expected.");
    return nullptr;
  }

  // Create a new Promise and its associated deferred object
  napi_value promise = nullptr;
  napi_deferred deferred = nullptr;
  NAPI_CALL(env, napi_create_promise(env, &deferred, &promise));

  // Allocate context data and store the deferred object
  auto addonData = new AddonData();
  addonData->deferred = deferred;

  // Store arguments
  NAPI_CALL(env, napi_get_value_double(env, args[0], &addonData->args[0]));
  NAPI_CALL(env, napi_get_value_double(env, args[1], &addonData->args[1]));

  // Create the asynchronous work item
  napi_value resourceName = nullptr;
  napi_create_string_utf8(env, "addPromise", NAPI_AUTO_LENGTH, &resourceName);
  NAPI_CALL(env, napi_create_async_work(env, nullptr, resourceName,
                                         addExecuteCB, addPromiseCompleteCB,
                                         static_cast<void>(addonData),
                                         &addonData->asyncWork));

  // Queue the asynchronous work item
  NAPI_CALL(env, napi_queue_async_work(env, addonData->asyncWork));

  // Return the Promise object to JavaScript
  return promise;
}

// Function to register NAPI properties
static napi_value registerFunc(napi_env env, napi_value exports) {
  // Using napi_property_descriptor for defining properties
  napi_property_descriptor desc[] = {
      { "addPromise", nullptr, addPromise, nullptr, nullptr, nullptr, napi_default, nullptr }
  };
  NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc));
  return exports;
}

// Module definition structure
static napi_module hellonapiModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = registerFunc,
    .nm_modname = "hellonapi",
    .nm_priv = nullptr,
    .reserved = {0},
};

// Module registration function
extern "C" __attribute__((constructor)) void hellonapiModuleRegister() {
  napi_module_register(&hellonapiModule);
}
</void></addondata></addondata>

index.ets (Promise Example)

import prompt from '@system.prompt';
import hellonapi from '@ohos.hellonapi';

@Entry
@Component
struct TestAdd {
  build() {
    Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
      Button("hellonapi.addPromise(x, y).then(...)")
        .margin(1)
        .fontSize(20)
        .onClick(() => {
          const num1 = 123;
          const num2 = 456;
          hellonapi.addPromise(num1, num2).then((result) => {
            prompt.showToast({ message: `hellonapi.addPromise(${num1}, ${num2}) = ${result}` });
          });
        });
    }
    .width('100%')
    .height('100%');
  }
}

@ohos.hellonapi.d.ts (Promise Example)

declare namespace hellonapi {
    function addPromise(num1: number, num2: number): Promise<number>;
    /**
     * @since 9
     * @syscap SystemCapability.Ability.AbilityRuntime.AbilityCore
     */
}
export default hellonapi;

Creating a Promise

The napi_create_promise() function is used to create a JavaScript Promise. It returns two essential objects: a napi_deferred object, which is used to resolve or reject the Promise later, and the napi_value representing the Promise itself. The napi_deferred object is stored in the AddonData structure for access in the completion callback.

Context Initialization (Promise)

Similar to the Callback model, a context structure (AddonData) is used. For the Promise model, this structure includes the napi_deferred object.

Creating and Queuing Async Work (Promise)

The process of creating and queuing the asynchronous work item using napi_create_async_work() and napi_queue_async_work() is identical to the Callback example. The key difference lies in the completion callback (addPromiseCompleteCB) and how the result is returned.

Execute Callback Handling (Promise)

The addExecuteCB function remains unchanged, performing the core computation in a worker thread.

Complete Callback Handling (Promise)

In the addPromiseCompleteCB function, instead of calling a JavaScript callback, we use napi_resolve_deferred() to fulfill the Promise with the computed result. This signals the successful completion of the asynchronous operation to any attached .then() handlers in JavaScript. Resource cleanup (deleting napi_ref, async work, and context data) follows, similar to the Callback example.

Unified Asynchronous Interface Example

This example demonstrates how to create a single native function that supports both Callback and Promise patterns based on the arguments provided by the JavaScript caller.

hellonapi.cpp (Unified Example)

#include <string.h>
#include <stdio.h>
#include "napi/native_node_api.h"
#include "napi/native_api.h"

// Structure to hold context data
struct AddonData {
  napi_async_work asyncWork = nullptr;
  napi_deferred deferred = nullptr; // For Promise
  napi_ref callback = nullptr;      // For Callback
  double args[2] = {0};
  double result = 0;
};

// Execute callback - performs the business logic
static void addExecuteCB(napi_env env, void *data) {
  AddonData *addonData = static_cast<addondata>(data);
  addonData->result = addonData->args[0] + addonData->args[1];
}

// Complete callback for the Callback pattern
static void addCallbackCompleteCB(napi_env env, napi_status status, void *data) {
  AddonData *addonData = static_cast<addondata>(data);
  napi_value callback = nullptr;
  napi_get_reference_value(env, addonData->callback, &callback);
  napi_value undefined = nullptr;
  napi_get_undefined(env, &undefined);
  napi_value result = nullptr;
  napi_create_double(env, addonData->result, &result);
  napi_value callbackResult = nullptr;
  napi_call_function(env, undefined, callback, 1, &result, &callbackResult);

  if (addonData->callback != nullptr) {
    napi_delete_reference(env, addonData->callback);
  }
  napi_delete_async_work(env, addonData->asyncWork);
  delete addonData;
}

// Complete callback for the Promise pattern
static void addPromiseCompleteCB(napi_env env, napi_status status, void *data) {
  AddonData *addonData = static_cast<addondata>(data);
  napi_value result = nullptr;
  napi_create_double(env, addonData->result, &result);
  napi_resolve_deferred(env, addonData->deferred, result);

  if (addonData->callback != nullptr) { // Cleanup if a callback reference was somehow kept
    napi_delete_reference(env, addonData->callback);
  }
  napi_delete_async_work(env, addonData->asyncWork);
  delete addonData;
}

// Unified native function handling both Promise and Callback
static napi_value addAsync(napi_env env, napi_callback_info info) {
  size_t argc = 3; // Max arguments: num1, num2, callback
  napi_value args[3];
  napi_value thisArg = nullptr;
  NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, &thisArg, nullptr));

  // Validate the first two arguments (numbers)
  napi_valuetype valuetype0;
  NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0));
  napi_valuetype valuetype1;
  NAPI_CALL(env, napi_typeof(env, args[1], &valuetype1));
  if (valuetype0 != napi_number || valuetype1 != napi_number) {
    napi_throw_type_error(env, nullptr, "Wrong arguments. 2 numbers expected.");
    return nullptr;
  }

  // Allocate context data
  auto addonData = new AddonData();

  // Determine if it's a Promise or Callback call based on argument count
  if (argc == 2) { // Promise call (num1, num2)
    napi_value promise = nullptr;
    napi_deferred deferred = nullptr;
    NAPI_CALL(env, napi_create_promise(env, &deferred, &promise));
    addonData->deferred = deferred;

    NAPI_CALL(env, napi_get_value_double(env, args[0], &addonData->args[0]));
    NAPI_CALL(env, napi_get_value_double(env, args[1], &addonData->args[1]));

    napi_value resourceName = nullptr;
    napi_create_string_utf8(env, "addPromise", NAPI_AUTO_LENGTH, &resourceName);
    NAPI_CALL(env, napi_create_async_work(env, nullptr, resourceName,
                                           addExecuteCB, addPromiseCompleteCB,
                                           static_cast<void>(addonData),
                                           &addonData->asyncWork));
    NAPI_CALL(env, napi_queue_async_work(env, addonData->asyncWork));
    return promise; // Return the Promise object

  } else { // Callback call (num1, num2, callback)
    napi_valuetype valuetype2;
    NAPI_CALL(env, napi_typeof(env, args[2], &valuetype2));
    if (valuetype2 != napi_function) {
      napi_throw_type_error(env, nullptr, "Callback function expected.");
      return nullptr;
    }

    NAPI_CALL(env, napi_get_value_double(env, args[0], &addonData->args[0]));
    NAPI_CALL(env, napi_get_value_double(env, args[1], &addonData->args[1]));
    NAPI_CALL(env, napi_create_reference(env, args[2], 1, &addonData->callback)); // Create reference to callback

    napi_value resourceName = nullptr;
    napi_create_string_utf8(env, "addCallback", NAPI_AUTO_LENGTH, &resourceName);
    NAPI_CALL(env, napi_create_async_work(env, nullptr, resourceName,
                                           addExecuteCB, addCallbackCompleteCB,
                                           static_cast<void>(addonData),
                                           &addonData->asyncWork));
    NAPI_CALL(env, napi_queue_async_work(env, addonData->asyncWork));

    napi_value result = nullptr;
    NAPI_CALL(env, napi_get_null(env, &result));
    return result; // Return null for callback pattern
  }
}

// Module registration function
static napi_value registerFunc(napi_env env, napi_value exports) {
  napi_property_descriptor desc[] = {
      DECLARE_NAPI_FUNCTION("addAsync", addAsync),
  };
  NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc));
  return exports;
}

// Module definition
static napi_module hellonapiModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = registerFunc,
    .nm_modname = "hellonapi",
    .nm_priv = nullptr,
    .reserved = {0},
};

// Module registration
extern "C" __attribute__((constructor)) void hellonapiModuleRegister() {
  napi_module_register(&hellonapiModule);
}
</void></void></addondata></addondata></addondata>

index.ets (Unified Example)

import prompt from '@system.prompt';
import hellonapi from '@ohos.hellonapi';

@Entry
@Component
struct TestAdd {
  build() {
    Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
      Button("hellonapi.addAsync(x, y, callback)")
        .margin(10)
        .fontSize(20)
        .onClick(() => {
          const num1 = 123;
          const num2 = 456;
          hellonapi.addAsync(num1, num2, (result) => {
            prompt.showToast({ message: `hellonapi.addAsync(${num1}, ${num2}) = ${result}` });
          });
        });

      Button("hellonapi.addAsync(x, y).then(...)")
        .margin(10)
        .fontSize(20)
        .onClick(() => {
          const num1 = 123;
          const num2 = 456;
          hellonapi.addAsync(num1, num2).then((result) => {
            prompt.showToast({ message: `hellonapi.addAsync(${num1}, ${num2}) = ${result}` });
          });
        });
    }
    .width('100%')
    .height('100%');
  }
}

@ohos.hellonapi.d.ts (Unified Example)

declare namespace hellonapi {
    function addAsync(num1: number, num2: number, callback: (result: number) => void): void;
    function addAsync(num1: number, num2: number): Promise<number>;
    /**
     * @since 9
     * @syscap SystemCapability.Ability.AbilityRuntime.AbilityCore
     */
}
export default hellonapi;

Synchronous vs. Asynchronous Interface Definitions (.ts)

Synchronous Methods

Synchronous methods block the JavaScript thread until they return a value. They are typically named using a verb followed by "Sync" (e.g., getValueSync()).

  • Format:

  • No parameters: methodName()

  • With parameters: methodNameSync(requiredParam[, optionalParam])

  • Return Value: Always returns a value.

Declaration File Template (Synchronous)

declare namespace moduleName {
    /**
     * Method description
     * @note Special notes
     * @since (Optional: specify if different from module version)
     * @sysCap System capability
     * @devices Supported devices (Optional: specify if different from module devices)
     * @param parameter Parameter description (Optional)
     * @return Return value description (Optional)
     */

    // No parameters
    function methodNameSync(): ReturnType;

    // With parameters
    function methodNameSync(requiredParam: ParamType, options?: OptionalParamType): ReturnType;

    interface OptionalParamType {
        paramName: ParamType;
    }
}
export default moduleName;

Example (Synchronous)

declare namespace hellonapi {
    function add(num1: number, num2: number): number;
    /**
     * @since 9
     * @syscap SystemCapability.Ability.AbilityRuntime.AbilityCore
     */
}
export default hellonapi;

Asynchronous Methods

Asynchronous methods do not block the calling thread. They are typically named using a verb or verb + noun (e.g., getValue()).

  • Format:

  • No parameters: methodName([callback])

  • With parameters: methodName(requiredParam[, optionalParam][, callback])

  • Return Value:

  • If a callback is provided: void

  • If no callback is provided: Returns a Promise object.

Declaration File Tepmlate (Asynchronous)

declare namespace moduleName {
    /**
     * Method description
     * @note Special notes
     * @since (Optional: specify if different from module version)
     * @sysCap System capability
     * @devices Supported devices (Optional: specify if different from module devices)
     * @param parameter Parameter description (Optional)
     */

    // No parameters
    function methodName(callback: AsyncCallback<ResultDataType>): void;
    function methodName(): Promise<ResultDataType>;

    // With parameters
    function methodName(requiredParam: ParamType, callback: AsyncCallback<ResultDataType>): void;
    function methodName(requiredParam: ParamType, options: OptionalParamType, callback: AsyncCallback<ResultDataType>): void;
    function methodName(requiredParam: ParamType, options?: OptionalParamType): Promise<ResultDataType>;

    interface OptionalParamType {
        paramName: ParamType;
    }
}
export default moduleName;

Example (Asynchronous)

declare namespace hellonapi {
    function addAsync(num1: number, num2: number, callback: (result: number) => void): void;
    function addAsync(num1: number, num2: number): Promise<number>;
    /**
     * @since 9
     * @syscap SystemCapability.Ability.AbilityRuntime.AbilityCore
     */
}
export default hellonapi;

NAPI Data Types

OpenHarmony's NAPI uses data types consistent with Node.js N-API. The fundamental NAPI data types can be found in the header files, such as js_native_api_types.h. These types, like napi_env and napi_value, are often opaque pointers for ABI stability, meaning their internal structure is not exposed directly but they function as well-defined types within the NAPI ecosystem.

typedef struct napi_env__* napi_env; is an example of an opaque pointer definition. While the underlying structure napi_env__ might not be directly defined in the header, the typedef creates a distinct type name for the pointer, ensuring type safety at compile time.

List of Exported Symbols in NAPI Standard Library

NAPI is based on the Node.js N-API specification. The list of exported symbols in the NAPI standard library is largely consistent with Node.js N-API. For version 3.2 Beta 3, the ported Node.js version is 14.19.1. You can refer to the Node.js N-API documentation for version 14.x for a comprehensive list.

Symbol Type Symbol Name Remarks
FUNC napi_module_register
FUNC napi_get_last_error_info
FUNC napi_throw
FUNC napi_throw_error
FUNC napi_throw_type_error
FUNC napi_throw_range_error
FUNC napi_is_error
FUNC napi_create_error
FUNC napi_create_type_error
FUNC napi_create_range_error
FUNC napi_get_and_clear_last_exception
FUNC napi_is_exception_pending
FUNC napi_fatal_error
FUNC napi_open_handle_scope
FUNC napi_close_handle_scope
FUNC napi_open_escapable_handle_scope
FUNC napi_close_escapable_handle_scope
FUNC napi_escape_handle
FUNC napi_create_reference
FUNC napi_delete_reference
FUNC napi_reference_ref
FUNC napi_reference_unref
FUNC napi_get_reference_value
FUNC napi_create_array
FUNC napi_create_array_with_length
FUNC napi_create_arraybuffer
FUNC napi_create_external
FUNC napi_create_external_arraybuffer
FUNC napi_create_object
FUNC napi_create_symbol
FUNC napi_create_typedarray
FUNC napi_create_dataview
FUNC napi_create_int32
FUNC napi_create_uint32
FUNC napi_create_int64
FUNC napi_create_double
FUNC napi_create_string_latin1
FUNC napi_create_string_utf8
FUNC napi_get_array_length
FUNC napi_get_arraybuffer_info
FUNC napi_get_prototype
FUNC napi_get_typedarray_info
FUNC napi_get_dataview_info
FUNC napi_get_value_bool
FUNC napi_get_value_double
FUNC napi_get_value_external
FUNC napi_get_value_int32
FUNC napi_get_value_int64
FUNC napi_get_value_string_latin1
FUNC napi_get_value_string_utf8
FUNC napi_get_value_uint32
FUNC napi_get_boolean
FUNC napi_get_global
FUNC napi_get_null
FUNC napi_get_undefined
FUNC napi_coerce_to_bool
FUNC napi_coerce_to_number
FUNC napi_coerce_to_object
FUNC napi_coerce_to_string
FUNC napi_typeof
FUNC napi_instanceof
FUNC napi_is_array
FUNC napi_is_arraybuffer
FUNC napi_is_typedarray
FUNC napi_is_dataview
FUNC napi_is_date
FUNC napi_strict_equals
FUNC napi_get_property_names
FUNC napi_set_property
FUNC napi_get_property
FUNC napi_has_property
FUNC napi_delete_property
FUNC napi_has_own_property
FUNC napi_set_named_property
FUNC napi_get_named_property
FUNC napi_has_named_property
FUNC napi_set_element
FUNC napi_get_element
FUNC napi_has_element
FUNC napi_delete_element
FUNC napi_define_properties
FUNC napi_call_function
FUNC napi_create_function
FUNC napi_get_cb_info
FUNC napi_get_new_target
FUNC napi_new_instance
FUNC napi_define_class
FUNC napi_wrap
FUNC napi_unwrap
FUNC napi_remove_wrap
FUNC napi_create_async_work
FUNC napi_delete_async_work
FUNC napi_queue_async_work
FUNC napi_cancel_async_work
FUNC napi_get_node_version
FUNC napi_get_version
FUNC napi_create_promise
FUNC napi_resolve_deferred
FUNC napi_reject_deferred
FUNC napi_is_promise
FUNC napi_run_script
FUNC napi_get_uv_event_loop

Native API Interface Descriptions

Symbol Type Symbol Name Remarks
FUNC napi_run_script_path Executes a JavaScript file.

Compiling Mirror Files

When compiling the OpenHarmony standard system image for the first time, the following files are generated: boot_linux.img, config.cfg, MiniLoaderAll.bin, parameter.txt, ramdisk.img, resource.img, system.img, uboot.img, updater.img, userdata.img, and vendor.img.

Subsequent compilations after modifying source code (excluding kernel code) allow for flashing only the modified image files, such as system.img, vendor.img, updater.img, userdata.img, and ramdisk.img.

Tags: NAPI OpenHarmony Asynchronous Programming callback Promise

Posted on Sun, 20 Sep 2026 16:19:52 +0000 by scliburn