[Node.js] Async Event Listener

Falcon·2021년 12월 30일
1

javascript

목록 보기
11/28

글의 목적

  1. Event 에 비동기 함수를 리스너로 등록해도 순서가 지켜지는가?
  2. 비동기 함수 리스너 등록시 주의해야할 점은 없는가?

비동기 함수, 리스너 순서가 지켜지는가?

Test 1

import EventEmitter from "events";

async function printIndexAndTime(idx: number) {
    console.log('index: %d , %s', idx, new Date().toISOString().split('T')[1]);
}

const logger = new EventEmitter();
// 이벤트 리스너 등록
logger.on('timeCheck', printIndexAndTime);

for (let idx = 1; idx <= 5 ; idx++) {
    logger.emit('timeCheck', idx)
}

// 모두 emit 한 순서대로 출력.
// Prints:
//index: 1 , 01:23:34.446Z
//index: 2 , 01:23:34.447Z
//index: 3 , 01:23:34.447Z
//index: 4 , 01:23:34.447Z
//index: 5 , 01:23:34.447Z

🤔 비동기 함수는 결과를 기다리지 않고 다음 Job 을 콜스택에 넣을텐데?

Synchronously calls each of the listeners registered for the event named eventName, in the order they were registered, passing the supplied arguments to each.

- Node.js document

주의할 점?

  • 비동기 함수 리스너는 return value 를 무시한다.
  • Exception 을 잡아내지 못한다. (Unhandled Promise Rejection)

test1

import EventEmitter from "events";

const logger = new EventEmitter();

async function printIndexAndTime(idx: number) {
    throw new Error('gg');
}

logger.on('timeCheck', printIndexAndTime);
// 에러를 잡아내지 못함
// Print:
// (node:5122) UnhandledPromiseRejectionWarning: Error: gg

logger.on('error', console.error); 

logger.emit('timeCheck2', 1);

test2

import EventEmitter from "events";
EventEmitter.captureRejections = true;

const logger = new EventEmitter();

function isEven(num : number) {
    return new Promise((resolve, reject)=>{
        if (num.constructor !== Number) reject("This is not a number")
        else if (num % 2 === 0) resolve(true)
        else resolve(false);
    });
}

function isEvenSync(num : number) {
    return num % 2 === 0;
}

async function printIndexAndTime(idx: number) {
    // throw new Error('gg');
    console.log('index: %d , %s', idx, new Date().toISOString().split('T')[1]);

    // const even : any = await isEven(idx);
    const even : any = await isEven(idx);

    console.log('is even? :', even);
}

logger.on('timeCheck', printIndexAndTime);
logger.on('error', console.error);

for (let idx = 1; idx <= 5 ; idx++) {
    logger.emit('timeCheck', idx);
}
index: 1 , 07:16:38.618Z
index: 2 , 07:16:38.619Z
index: 3 , 07:16:38.619Z
index: 4 , 07:16:38.619Z
index: 5 , 07:16:38.619Z
is even? : false
is even? : true
is even? : false
is even? : true
is even? : false

⚠️ 모든 listener 가 순차적으로 실행된 후에 await isEven() 이 실행된다.

🤔 emit() 된 리스너 함수는 전부 '콜스택'으로 들어가나? (~ing)

그래서 async 함수의 결과가 Job Queue 에 추가되어
콜스택이 비워진 후에 Job Queue 가 실행되는 것일지 모르겠다. (미완)

해결방안 - captureRejections 옵션

아직 experimental 이지만 이제 eventEmitter 에서 비동기 함수의 exception 도 잡아낼 수 있다.

test2

import EventEmitter from "events";
// 옵션 추가
EventEmitter.captureRejections = true;

const logger = new EventEmitter();

async function printIndexAndTime(idx: number) {
    throw new Error('gg');
    console.log('index: %d , %s', idx, new Date().toISOString().split('T')[1]);
}

logger.on('timeCheck', printIndexAndTime);
logger.on('error', console.error);

logger.emit('timeCheck2', 1);

// Print:
// Error: gg
// (이하 생략)

📝 결론

비동기 리스너를 등록했다 하더라도
1. JobQueue 에 들어가는 순서는 지켜진다.
(emit 한 순서대로 Job Queue 에 쌓인다.)
2. 리스너는 항상 등록된 순서대로 호출된다.
3. captureRejections 옵션을 사용하면 비동기 리스너의 에러도 핸들링이 가능하다.


🔗 Reference

profile
I'm still hungry

0개의 댓글