출처 : https://z-wony.tistory.com/18

Node.js addon async callback 작성 방법 1

1. 파라미터로 받은 Function 객체를 Synchronous 하게 call

js 함수명: directCall, c++ 함수명: MeethodFunc

void MethodFunc(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();

  // 콜백 함수가 파라미터로 넘어오지 않으면 에러 발생
  if (args.Length() < 1 || !args[0]->IsFunction()) {
    Local<String> err_msg = String::NewFromUtf8(isolate, "Invalid Parameter");
    Local<Value> exception = v8::Exception::Error(err_msg);
    isolate->ThrowException(exception);
    return;
  }

  Local<Function> cb = Local<Function>::Cast(args[0]);
  // "->" 연산자로 Function Class instance 참조해 Function Class 의 Call 메소드 호출
  cb->Call(Null(isolate), 0, NULL);
}

테스트를 위한 js 코드

const m = require('./test.node');

console.log('----- Test 02 directCall positive -----');
m.directCall(function (text) {
  console.log('In callback: ' + text);
});
console.log('----------------------------');
console.log();

console.log('----- Test 02 directCall negative -----');
try {
  m.directCall('');
} catch (err) {
  console.error('catch: ' + err);
}
console.log('----------------------------');

결과

----- Test 02 directCall positive -----
In callback: undefined
----------------------------

----- Test 02 directCall negative -----
catch: Error: Invalid Parameter
----------------------------


2. 파라미터로 받은 Function 객체를 Asynchronous 하게 call

libuv 의 timer 를 사용해 다음 event loop 에서 callback 이 호출되는 상황을 구현한다.

js 함수명: asyncCall, c++ 함수명: MethodTimer

void MethodTimer(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();

  if (args.Length() < 1 || !args[0]=>IsFunction()) {
    Local<String> err_msg = String:NewFromUtf8(isolate, "Invalid Parameter");
    Local<Value> exception = v8:Exception:Error(err_msg);
    isolate->ThrowException(exception);
    return;
  }

  // Node.js 에서 동작하는 libuv 의 default loop 를 가져와서 uv timer handle 을 초기화 한다.
  uv_loop_t* loop = uv_default_loop();
  uv_timer_t* timer = (uv_timer_t *)malloc(sizeof(uv_timer_t));
  uv_timer_init(loop, timer);

  Local<Function> cb = Local<Funtion>::Cast(args[0]);

  // Persistent<Function> 을 new 로 생성하면서 Local<Function> 을 인자로 전달한다.
  // js 함수에게 받은 Function Object(args[0] == Local<Function> cb) 가
  // MethodTimer 함수가 종료되어 지역 변수 영역이 소멸되어도 GC 되지 않도록 한다.
  Persistent<Function> *pf = new Persistent<Function>(isolate, cb);
  // Persistent<Function> 을 libuv timer 함수에서 사용하기 위해 timer->data 에 보관
  timer->data = (void *)pf;

  // Async Timer 실행
  uv_timer_start(timer, _timer_cb, 1000, 1000);
}

static int g_timer_count = 0;
static int g_timer_max = 2;

void _timer_cb(uv_timer_t *timer) {
  Isolate *isolate = Isolate::GetCurrent();

  // HandleScope Class 를 선언해 LocalHandle 이 생성될 수 있고 _timer_cb 함수
  // 종료 시 GC 가 메모리를 관리할 수 있게 한다.
  HandleScope handle_scopt(isolate);
  Persistent<Function> *pf = (Persistent<Function> *)timer->data;

  Local<Value> argv[1] = {
    String::NewFromUtf8(isolate, "Timer invoked")
  };

  Local<Object> global= isolate->GetCurrentContext()->Global();
  // c++ 의 new 가 아닌 Local Class 의 New 함수를 사용.
  // 실제 Object 의 메모리 주소를 Handle 두 개가 참조하고 있다.
  // 참조를 모두 해제해야 GC 가 메모리를 해제할 수 있다.
  Local<Function> callback = Local<Function>::New(isolate, *pf);

  callbck->Call(global, 1, argv);

  // _timer_cb 가 두 번 호출되도록 한다.
  g_timer_count++;
  if (g_timer_count >= g_timer_max) {
    // 두 번째 호출 되었을 때 Persistent<Function> 의 Rest() 함수를 호출해 메모리 참조를 초기화 한다.
    // 이제 _timer_cb 가 종료되면 handle_scope 의 소멸자가 호출되고
    // Local<Function> callback 의 메모리 참조도 초기화 된다.
    // 실제 Object 의 메모리를 아무도 참조하지 않게 되참 메모리가 반환된다.
    pf->Rest();
    delete pf;

    // timer 을 중단하고 timer handle 의 메모리를 해제
    uv_timer_stop(timer);
    free(timer);
  }
}

테스트를 위한 js 코드

const m = require('./test.node');

console.log('----- Test 03 asyncCall positive -----');
m.asyncCall(function (text) {
  console.log('In async callback: ' + text);
});
console.log('---------------------------');
console.log();

실행 결과

----- Test 03 asyncCall positive -----
---------------------------

In async callback: Timer invoked
In async callback: Timer invoked