OpenHarmony NAPI 비동기 인터페이스 개요
OpenHarmony의 NAPI(Native API)는 C/C++ 모듈이 JavaScript 런타임 환경과 효율적으로 상호작용할 수 있도록 지원하는 표준화된 인터페이스입니다. NAPI를 통해 개발자는 고성능 C/C++ 코드를 JavaScript 애플리케이션에 통합할 수 있습니다. 특히 비동기 인터페이스는 메인 스레드를 블로킹하지 않고 장시간 실행되는 작업을 처리할 수 있어, 애플리케이션의 응답성을 유지하는 데 필수적입니다.
NAPI 비동기 인터페이스는 크게 두 가지 방식으로 구현될 수 있습니다:
- 콜백(Callback) 방식: 비동기 작업 완료 시 호출될 JavaScript 함수를 인자로 전달하는 방식입니다.
- 프로미스(Promise) 방식: 비동기 작업의 결과를 나타내는 Promise 객체를 반환하며,
.then()및.catch()메서드를 통해 비동기 결과를 처리합니다.
OpenHarmony NAPI 표준 구현 지침에 따르면, JavaScript 엔진이 Promise 기능을 지원하는 경우, 비동기 메서드는 콜백 방식과 프로미스 방식을 모두 지원하도록 구현하는 것이 권장됩니다. 어떤 방식을 사용할지는 애플리케이션 개발자가 결정하며, NAPI 함수 호출 시 콜백 함수를 전달하는지 여부로 구분됩니다. 콜백 함수를 전달하지 않으면 Promise 방식이 적용되며, NAPI 함수는 Promise 인스턴스를 반환하게 됩니다.
비동기 처리 메커니즘
NAPI를 사용한 동기(Synchronous) 방식의 작업은 모든 코드가 네이티브 메서드(일반적으로 JavaScript 메인 스레드) 내에서 즉시 실행됩니다. 이는 빠른 작업에는 적합하지만, 시간이 오래 걸리는 작업의 경우 메인 스레드를 블로킹하여 UI 멈춤과 같은 사용자 경험 저하를 초래할 수 있습니다. 이러한 문제를 해결하기 위해 NAPI는 비동기(Asynchronous) 방식을 지원합니다.
비동기 처리의 핵심은 NAPI 프레임워크가 제공하는 napi_create_async_work() 함수를 활용하는 것입니다. 이 함수는 백그라운드에서 실행될 비동기 작업을 생성하고 관리합니다. 비동기 NAPI 함수가 호출될 때의 일반적인 처리 흐름은 다음과 같습니다:
- 인자 수신 및 컨텍스트 초기화: JavaScript에서 NAPI 비동기 함수가 호출되면, 네이티브 C/C++ 함수는 전달된 인자를 수신하고 필요한 데이터로 변환합니다. 이 데이터는 비동기 작업 전반에 걸쳐 상태를 유지하기 위한 사용자 정의 컨텍스트 구조체(예:
CalculationContext)에 저장됩니다. - 비동기 작업 객체 생성:
napi_create_async_work()함수를 호출하여 비동기 작업 객체를 생성합니다. 이 때 비즈니스 로직을 실행할execute_callback함수와 작업 완료 후 결과를 처리할complete_callback함수를 지정합니다. - 작업 큐잉 및 즉시 반환: 생성된 비동기 작업 객체는
napi_queue_async_work()함수를 통해 NAPI 내부의 스케줄링 큐에 추가됩니다. 네이티브 함수는 이 시점에서 즉시 반환되며, 콜백 방식의 경우napi_undefined를, 프로미스 방식의 경우 새로 생성된 Promise 객체를 반환합니다. 이로써 JavaScript 메인 스레드는 블로킹 없이 다음 작업을 계속할 수 있습니다. - 백그라운드 작업 실행 (`execute_callback`): NAPI의 비동기 작업 스레드 풀에서 스케줄러가 큐에 있는 작업을 가져오면, 지정된
execute_callback함수가 워커 스레드에서 실행됩니다. 이 함수는 컨텍스트 데이터에서 입력 값을 읽어 실제 비즈니스 로직(예: 복잡한 계산, I/O 작업)을 수행하고, 그 결과를 다시 컨텍스트 데이터에 저장합니다. 이 단계는 메인 스레드를 전혀 블록하지 않습니다. - 결과 처리 (`complete_callback`):
execute_callback이 성공적으로 완료되거나 특정 사유로 취소된 후에는, NAPI 내부 이벤트 루프에 의해complete_callback함수가 JavaScript 메인 스레드에서 호출됩니다. 이 함수는 컨텍스트 데이터에서 처리 결과를 가져와 JavaScript가 이해할 수 있는napi_value형태로 변환합니다. 최종적으로 이 결과는 JavaScript 콜백 함수를 호출하거나(콜백 방식), Promise를resolve또는reject하는 방식으로 JavaScript 환경에 전달됩니다. 작업에 사용된 모든 NAPI 자원(예:napi_async_work,napi_ref)은 이 단계에서 해제되어야 합니다.
napi_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_callback,
napi_async_complete_callback complete_callback,
void* data,
napi_async_work* out_work_handle
);
매개변수는 다음과 같은 역할을 수행합니다:
env: 현재 NAPI 환경 포인터로, JS 엔진과 관련된 정보를 담고 있습니다. 프레임워크에서 제공하며 일반적으로 그대로 전달합니다.async_resource: (선택 사항) Node.js의async_hooksAPI와 연관된 리소스 객체입니다.async_resource_name: 비동기 작업의 식별자 역할을 하는 문자열로, 디버깅 및 진단 목적으로 활용될 수 있습니다.execute_callback: 백그라운드 워커 스레드에서 실행될 실제 비즈니스 로직 함수입니다. I/O 작업이나 CPU 집약적인 계산을 수행하여 메인 스레드의 블로킹을 방지합니다.complete_callback:execute_callback이 완료되거나 취소된 후 메인 스레드(Event Loop)에서 호출되는 함수입니다. 이 함수에서 결과 처리 및 JavaScript로의 값 전달이 이루어집니다.data: 비동기 작업 전반에 걸쳐 데이터를 전달하는 데 사용되는 사용자 정의 컨텍스트 구조체에 대한 포인터입니다.out_work_handle: 성공적으로 생성된napi_async_work객체의 핸들을 반환하는 포인터입니다.
반환 값: napi_ok는 성공을, 그 외의 값은 오류를 나타냅니다.
콜백 기반 비동기 인터페이스 구현
여기서는 간단한 덧셈 기능을 콜백 방식으로 구현하는 예시를 통해 NAPI 비동기 인터페이스의 구현 방법을 설명합니다. JavaScript (eTS) 측의 함수 정의는 다음과 같습니다.
function calculateAsyncWithCallback(numA: number, numB: number, callback:(result: number) => void): void;
컨텍스트 데이터 초기화
비동기 작업을 수행하는 동안 필요한 데이터를 저장하고 전달하기 위한 사용자 정의 C++ 구조체를 정의합니다. 이 구조체는 비동기 작업 핸들, 콜백 함수 참조, 입력 인자, 계산 결과 등을 포함합니다.
struct CalculationContext {
napi_async_work work_handle = nullptr;
napi_ref callback_ref = nullptr; // JavaScript 콜백 함수 참조
double input_num1 = 0.0;
double input_num2 = 0.0;
double calculation_result = 0.0;
napi_status status_code = napi_ok; // 작업 실행 상태
std::string error_msg; // 오류 메시지
};
네이티브 NAPI 함수는 JavaScript에서 전달된 인자들을 받아서 CalculationContext 구조체에 저장합니다. 특히 JavaScript의 함수 객체(napi_value 타입)는 네이티브 메서드 호출이 끝난 후 생명 주기가 종료되므로, 백그라운드 스레드에서 접근할 수 없습니다. 이를 위해 NAPI는 napi_ref 타입을 제공하며, 이는 네이티브 메서드 생명 주기보다 긴 참조 생명 주기를 가집니다. 따라서 콜백 함수는 napi_create_reference()를 사용하여 napi_ref로 변환하여 저장해야 합니다.
static napi_value CalculateAsyncWithCallback(napi_env env, napi_callback_info info) {
size_t actual_arg_count = 3;
napi_value js_args[3];
// 실제 인자 개수를 포함하여 호출 정보를 가져옵니다.
NAPI_CALL(env, napi_get_cb_info(env, info, &actual_arg_count, js_args, nullptr, nullptr));
// 인자 유효성 검사 (첫 두 인자는 숫자, 세 번째 인자는 함수)
napi_valuetype type0, type1, type2;
NAPI_CALL(env, napi_typeof(env, js_args[0], &type0));
NAPI_CALL(env, napi_typeof(env, js_args[1], &type1));
NAPI_CALL(env, napi_typeof(env, js_args[2], &type2));
if (type0 != napi_number || type1 != napi_number || type2 != napi_function) {
napi_throw_type_error(env, nullptr, "Expecting two numbers and a function callback.");
return nullptr;
}
// 비동기 작업 컨텍스트 데이터 초기화
CalculationContext *context = new CalculationContext();
// JavaScript 인자를 컨텍스트에 저장
NAPI_CALL(env, napi_get_value_double(env, js_args[0], &context->input_num1));
NAPI_CALL(env, napi_get_value_double(env, js_args[1], &context->input_num2));
// 콜백 함수는 napi_ref로 변환하여 저장
NAPI_CALL(env, napi_create_reference(env, js_args[2], 1, &context->callback_ref));
// ... (다음 단계로 계속)
}
비동기 작업 객체 생성
napi_create_async_work()를 호출하여 비동기 작업을 생성하고, 생성된 작업을 napi_queue_async_work()를 통해 큐에 추가합니다. 네이티브 함수는 작업 큐잉 후 즉시 napi_undefined를 반환합니다.
static void ExecuteAdditionTask(napi_env env, void *data);
static void CompleteCallbackHandler(napi_env env, napi_status status, void *data);
static napi_value CalculateAsyncWithCallback(napi_env env, napi_callback_info info) {
// ... (인자 수신 및 컨텍스트 초기화 부분)
// 비동기 작업 객체 생성
napi_value resource_name;
NAPI_CALL(env, napi_create_string_utf8(env, "AddOperationAsyncCallback", NAPI_AUTO_LENGTH, &resource_name));
NAPI_CALL(env, napi_create_async_work(env, nullptr, resource_name,
ExecuteAdditionTask, CompleteCallbackHandler,
static_cast(context), &context->work_handle));
// 비동기 작업 큐에 추가
NAPI_CALL(env, napi_queue_async_work(env, context->work_handle));
// 네이티브 함수는 즉시 undefined를 반환
napi_value result_undefined;
NAPI_CALL(env, napi_get_undefined(env, &result_undefined));
return result_undefined;
}
execute_callback 함수 구현
execute_callback 함수는 워커 스레드에서 실행되며, 실제 비즈니스 로직을 수행합니다. 이 함수는 메인 스레드를 블록하지 않으므로, I/O 작업이나 CPU 집약적인 작업을 안전하게 처리할 수 있습니다. 여기서는 간단한 덧셈 연산을 수행합니다.
static void ExecuteAdditionTask(napi_env env, void *data) {
CalculationContext *context = static_cast<CalculationContext *>(data);
if (context == nullptr) {
// 컨텍스트가 유효하지 않으면 오류 상태를 설정
context->status_code = napi_generic_failure;
context->error_msg = "Invalid context data in execute task.";
return;
}
// 복잡한 계산 수행 (여기서는 간단한 덧셈)
context->calculation_result = context->input_num1 + context->input_num2;
context->status_code = napi_ok; // 성공적으로 완료
}
complete_callback 함수 구현
complete_callback 함수는 execute_callback 완료 후 메인 스레드에서 호출됩니다. 이 함수에서는 백그라운드에서 계산된 결과를 가져와 JavaScript 콜백 함수를 통해 애플리케이션에 전달합니다. NAPI 프레임워크는 C/C++ 코드에서 JavaScript 함수를 호출하기 위한 napi_call_function() 함수를 제공합니다.
NAPI_EXTERN napi_status napi_call_function(napi_env env,
napi_value recv,
napi_value func,
size_t argc,
const napi_value* argv,
napi_value* result);
env: NAPI 환경 포인터.recv: 호출될 함수의this객체.func: 호출할 JavaScript 함수.argc: 함수 인자의 개수.argv: 함수 인자 배열.result: 함수 실행의 반환 값을 받을 포인터.
complete_callback 구현 시 napi_ref로 저장된 콜백 함수를 napi_get_reference_value()를 통해 다시 napi_value로 변환해야 합니다. 작업 완료 후에는 napi_delete_reference()와 napi_delete_async_work()를 호출하여 생성된 NAPI 자원들을 해제해야 합니다.
static void CompleteCallbackHandler(napi_env env, napi_status status, void *data) {
CalculationContext *context = static_cast<CalculationContext *>(data);
if (context == nullptr) {
// 유효하지 않은 컨텍스트, 추가 처리 불가
return;
}
napi_value js_callback = nullptr;
if (context->callback_ref != nullptr) {
NAPI_CALL_NO_THROW(env, napi_get_reference_value(env, context->callback_ref, &js_callback));
}
napi_value global_obj;
NAPI_CALL_NO_THROW(env, napi_get_global(env, &global_obj));
napi_value result_to_pass;
// 비동기 작업 자체에 오류가 있거나 execute_callback에서 오류가 발생한 경우
if (status != napi_ok || context->status_code != napi_ok) {
// JS 콜백이 (result: number) => void 시그니처를 가정하므로, 오류 시 0을 전달
NAPI_CALL_NO_THROW(env, napi_create_double(env, 0.0, &result_to_pass));
// 실제 애플리케이션에서는 콜백 시그니처를 (error: string | null, result: number | null) 등으로 변경하여 오류를 명시적으로 전달하는 것이 좋습니다.
} else {
NAPI_CALL_NO_THROW(env, napi_create_double(env, context->calculation_result, &result_to_pass));
}
if (js_callback != nullptr) {
napi_value argv[1] = { result_to_pass };
NAPI_CALL_NO_THROW(env, napi_call_function(env, global_obj, js_callback, 1, argv, nullptr));
}
// NAPI 리소스 정리
if (context->callback_ref != nullptr) {
NAPI_CALL_NO_THROW(env, napi_delete_reference(env, context->callback_ref));
}
if (context->work_handle != nullptr) {
NAPI_CALL_NO_THROW(env, napi_delete_async_work(env, context->work_handle));
}
delete context;
}
eTS에서 콜백 인터페이스 호출
구현된 NAPI 함수는 JavaScript/eTS 코드에서 다음과 같이 호출할 수 있습니다.
import testNapi from "libentry.so"; // NAPI 모듈 로드
@Entry
@Component
struct Index {
build() {
Row() {
Column() {
Button('Calculate with Callback')
.fontSize(24)
.padding(10)
.onClick(() => {
let num1 = 123;
let num2 = 456;
// NAPI 비동기 콜백 함수 호출
testNapi.calculateAsyncWithCallback(num1, num2, (result) => {
console.info(`결과: ${num1} + ${num2} = ${result}`);
});
})
}
.width('100%')
}
.height('100%')
}
}
프로미스 기반 비동기 인터페이스 구현
프로미스(Promise) 방식 또한 콜백 방식과 유사한 비동기 처리 흐름을 따르지만, 결과 전달 방식이 다릅니다. JavaScript (eTS) 측의 함수 정의는 다음과 같습니다.
function calculateAsyncWithPromise(numA: number, numB: number): Promise<number>;
프로미스 생성
NAPI 프레임워크는 프로미스 객체를 생성하기 위한 napi_create_promise() 함수를 제공합니다. 이 함수는 프로미스 객체(napi_value)와 함께 프로미스를 resolve 또는 reject할 때 사용되는 napi_deferred 객체를 반환합니다. napi_deferred 객체는 컨텍스트 데이터에 저장되어 complete_callback 함수에서 사용됩니다.
napi_status napi_create_promise(napi_env env,
napi_deferred* deferred,
napi_value* promise);
env: NAPI 환경 포인터.deferred: 생성된napi_deferred객체를 받을 포인터. 이 객체는 나중에 프로미스를 resolve/reject하는 데 사용됩니다.promise: 생성된 JavaScriptPromise객체를 받을 포인터. 이 객체는 네이티브 함수가 즉시 반환하는 값입니다.
static napi_value CalculateAsyncWithPromise(napi_env env, napi_callback_info info) {
// ... (인자 수신 및 유효성 검사 부분)
// Promise 객체 및 deferred 객체 생성
napi_value js_promise = nullptr;
napi_deferred deferred_handle = nullptr;
NAPI_CALL(env, napi_create_promise(env, &deferred_handle, &js_promise));
// 컨텍스트 데이터 초기화 (이전 구조체에서 callback_ref 대신 deferred_promise 사용)
CalculationContext *context = new CalculationContext();
context->deferred_promise = deferred_handle; // deferred 객체를 컨텍스트에 저장
// ... (인자 저장 및 다음 단계로 계속)
}
컨텍스트 데이터 및 비동기 작업 객체 생성
프로미스 방식에서는 CalculationContext 구조체에서 callback_ref 대신 deferred_promise를 사용합니다. 비동기 작업 객체 생성 및 큐에 추가하는 과정은 콜백 방식과 동일합니다. 다만 네이티브 함수는 napi_undefined 대신 생성된 Promise 객체를 반환합니다.
struct CalculationContext {
napi_async_work work_handle = nullptr;
// napi_ref callback_ref = nullptr; // 콜백 방식에서 사용
napi_deferred deferred_promise = nullptr; // 프로미스 방식에서 사용
double input_num1 = 0.0;
double input_num2 = 0.0;
double calculation_result = 0.0;
napi_status status_code = napi_ok;
std::string error_msg;
};
// 프로미스 방식의 complete_callback 함수 선언
static void CompletePromiseHandler(napi_env env, napi_status status, void *data);
static napi_value CalculateAsyncWithPromise(napi_env env, napi_callback_info info) {
size_t actual_arg_count = 2; // 프로미스 방식은 인자 2개 (num1, num2)
napi_value js_args[2];
NAPI_CALL(env, napi_get_cb_info(env, info, &actual_arg_count, js_args, nullptr, nullptr));
// 인자 유효성 검사
napi_valuetype type0, type1;
NAPI_CALL(env, napi_typeof(env, js_args[0], &type0));
NAPI_CALL(env, napi_typeof(env, js_args[1], &type1));
if (type0 != napi_number || type1 != napi_number) {
napi_throw_type_error(env, nullptr, "Expecting two numbers for Promise call.");
return nullptr;
}
napi_value js_promise = nullptr;
napi_deferred deferred_handle = nullptr;
NAPI_CALL(env, napi_create_promise(env, &deferred_handle, &js_promise));
CalculationContext *context = new CalculationContext();
context->deferred_promise = deferred_handle;
NAPI_CALL(env, napi_get_value_double(env, js_args[0], &context->input_num1));
NAPI_CALL(env, napi_get_value_double(env, js_args[1], &context->input_num2));
// 비동기 작업 객체 생성 (execute_callback은 콜백 방식과 동일)
napi_value resource_name;
NAPI_CALL(env, napi_create_string_utf8(env, "AddOperationAsyncPromise", NAPI_AUTO_LENGTH, &resource_name));
NAPI_CALL(env, napi_create_async_work(env, nullptr, resource_name,
ExecuteAdditionTask, CompletePromiseHandler,
static_cast(context), &context->work_handle));
// 비동기 작업 큐에 추가
NAPI_CALL(env, napi_queue_async_work(env, context->work_handle));
// 네이티브 함수는 Promise 객체를 즉시 반환
return js_promise;
}
execute_callback 함수
프로미스 방식의 execute_callback 함수는 콜백 방식과 동일하게 구현됩니다. 워커 스레드에서 비즈니스 로직을 수행하고 결과를 컨텍스트에 저장합니다.
static void ExecuteAdditionTask(napi_env env, void *data) {
// ... 콜백 방식과 동일한 구현 ...
CalculationContext *context = static_cast<CalculationContext *>(data);
if (context == nullptr) {
context->status_code = napi_generic_failure;
context->error_msg = "Invalid context data in execute task.";
return;
}
context->calculation_result = context->input_num1 + context->input_num2;
context->status_code = napi_ok;
}
complete_callback 함수 (프로미스 전용)
프로미스 방식의 complete_callback에서는 napi_resolve_deferred() 또는 napi_reject_deferred() 함수를 사용하여 Promise를 resolve 또는 reject합니다. 이 함수들도 메인 스레드에서 호출되어야 합니다.
static void CompletePromiseHandler(napi_env env, napi_status status, void *data) {
CalculationContext *context = static_cast<CalculationContext *>(data);
if (context == nullptr) {
// 유효하지 않은 컨텍스트
return;
}
// 비동기 작업 자체에 오류가 있거나 execute_callback에서 오류가 발생한 경우
if (status != napi_ok || context->status_code != napi_ok) {
napi_value error_value;
std::string final_error_msg = (status != napi_ok) ? "Async work failed or cancelled." : context->error_msg;
if (final_error_msg.empty()) { // 에러 메시지가 없을 경우 기본 메시지
final_error_msg = "Calculation failed due to an unknown error.";
}
NAPI_CALL_NO_THROW(env, napi_create_string_utf8(env, final_error_msg.c_str(), NAPI_AUTO_LENGTH, &error_value));
NAPI_CALL_NO_THROW(env, napi_reject_deferred(env, context->deferred_promise, error_value));
} else {
napi_value result_value;
NAPI_CALL_NO_THROW(env, napi_create_double(env, context->calculation_result, &result_value));
NAPI_CALL_NO_THROW(env, napi_resolve_deferred(env, context->deferred_promise, result_value));
}
// NAPI 리소스 정리
if (context->work_handle != nullptr) {
NAPI_CALL_NO_THROW(env, napi_delete_async_work(env, context->work_handle));
}
delete context;
}
eTS에서 프로미스 인터페이스 호출
구현된 프로미스 기반 NAPI 함수는 JavaScript/eTS 코드에서 다음과 같이 호출할 수 있습니다.
import testNapi from "libentry.so"; // NAPI 모듈 로드
@Entry
@Component
struct Index {
build() {
Row() {
Column() {
Button('Calculate with Promise')
.fontSize(24)
.padding(10)
.margin({ top: 20 })
.onClick(() => {
let num1 = 123;
let num2 = 456;
// NAPI 비동기 Promise 함수 호출
testNapi.calculateAsyncWithPromise(num1, num2)
.then((result) => {
console.info(`Promise 결과: ${num1} + ${num2} = ${result}`);
})
.catch((error) => {
console.error(`Promise 에러: ${error}`);
});
})
}
.width('100%')
}
.height('100%')
}
}
단일 NAPI 함수로 콜백 및 프로미스 지원
서론에서 언급했듯이, NAPI 비동기 인터페이스는 콜백과 프로미스 방식을 모두 지원해야 합니다. 이는 단일 NAPI 네이티브 함수 내에서 호출 시 전달된 인자의 개수나 타입을 확인하여 어떤 방식으로 처리할지 결정함으로써 구현할 수 있습니다.
JavaScript/eTS 측의 함수 정의는 오버로드(overload) 형태로 제공될 수 있습니다:
function unifiedCalculateAsync(num1: number, num2: number, callback:(result: number) => void): void;
function unifiedCalculateAsync(num1: number, num2: number): Promise<number>;
컨텍스트 데이터 구조체 수정
콜백과 프로미스 두 가지 방식을 모두 지원하기 위해, 컨텍스트 구조체는 napi_ref와 napi_deferred를 모두 포함하도록 수정됩니다.
struct CalculationContext {
napi_async_work work_handle = nullptr;
napi_ref callback_ref = nullptr; // 콜백 방식용
napi_deferred deferred_promise = nullptr; // 프로미스 방식용
double input_num1 = 0.0;
double input_num2 = 0.0;
double calculation_result = 0.0;
napi_status status_code = napi_ok;
std::string error_msg;
};
단일 네이티브 함수 구현
unifiedCalculateAsync라는 단일 네이티브 함수 내에서 전달된 인자의 개수를 확인하여 처리 방식을 분기합니다. 예를 들어, 2개의 숫자 인자가 전달되면 Promise 방식으로, 2개의 숫자와 1개의 함수 인자가 전달되면 콜백 방식으로 처리합니다.
static napi_value UnifiedAddAsync(napi_env env, napi_callback_info info) {
size_t actual_arg_count = 3; // 최대 예상 인자 개수
napi_value js_args[3];
NAPI_CALL(env, napi_get_cb_info(env, info, &actual_arg_count, js_args, nullptr, nullptr));
// 첫 두 인자(숫자) 유효성 검사
napi_valuetype type0, type1;
NAPI_CALL(env, napi_typeof(env, js_args[0], &type0));
NAPI_CALL(env, napi_typeof(env, js_args[1], &type1));
if (type0 != napi_number || type1 != napi_number) {
napi_throw_type_error(env, nullptr, "Expected first two arguments to be numbers.");
return nullptr;
}
CalculationContext *context = new CalculationContext();
NAPI_CALL(env, napi_get_value_double(env, js_args[0], &context->input_num1));
NAPI_CALL(env, napi_get_value_double(env, js_args[1], &context->input_num2));
napi_value return_value = nullptr; // 이 함수가 반환할 값
if (actual_arg_count == 2) { // Promise 방식으로 처리
napi_value js_promise = nullptr;
napi_deferred deferred_handle = nullptr;
NAPI_CALL(env, napi_create_promise(env, &deferred_handle, &js_promise));
context->deferred_promise = deferred_handle;
return_value = js_promise; // Promise 객체 반환
napi_value resource_name;
NAPI_CALL(env, napi_create_string_utf8(env, "UnifiedAddPromise", NAPI_AUTO_LENGTH, &resource_name));
NAPI_CALL(env, napi_create_async_work(env, nullptr, resource_name,
ExecuteAdditionTask, CompletePromiseHandler,
static_cast(context), &context->work_handle));
} else if (actual_arg_count == 3) { // Callback 방식으로 처리
napi_valuetype type2;
NAPI_CALL(env, napi_typeof(env, js_args[2], &type2));
if (type2 != napi_function) {
napi_throw_type_error(env, nullptr, "Expected third argument to be a function for callback mode.");
delete context; // 에러 발생 시 컨텍스트 메모리 해제
return nullptr;
}
NAPI_CALL(env, napi_create_reference(env, js_args[2], 1, &context->callback_ref));
NAPI_CALL(env, napi_get_undefined(env, &return_value)); // undefined 반환
napi_value resource_name;
NAPI_CALL(env, napi_create_string_utf8(env, "UnifiedAddCallback", NAPI_AUTO_LENGTH, &resource_name));
NAPI_CALL(env, napi_create_async_work(env, nullptr, resource_name,
ExecuteAdditionTask, CompleteCallbackHandler,
static_cast(context), &context->work_handle));
} else {
napi_throw_type_error(env, nullptr, "Invalid number of arguments. Expected 2 or 3.");
delete context;
return nullptr;
}
NAPI_CALL(env, napi_queue_async_work(env, context->work_handle));
return return_value;
}
ExecuteAdditionTask, CompleteCallbackHandler, CompletePromiseHandler 함수는 위에서 설명된 내용과 동일하게 구현되며, CalculationContext 구조체의 적절한 멤버를 사용하여 데이터를 처리합니다.
이 방식으로 구현하면 JavaScript 개발자는 자신의 필요에 따라 유연하게 콜백 또는 프로미스 기반 비동기 호출을 선택하여 NAPI 기능을 활용할 수 있습니다.