-
Node.JS 디자인 패턴 및 바이블 (3)JS 2021. 7. 25. 17:11728x90
책의 이번 챕터는 콜백과 이벤트에 대해서 다룹니다.
🚀 콜백 패턴
콜백은 앞서 다루었던 비동기를 처리하는 리액터 패턴의 핸들러입니다.
쉽게 이야기하면 다른 함수에 인자로 전달되는 함수이며, 비동기를 처리할 때는 완료된 작업의 결과를 가지고 호출됩니다.
이때 콜백은 클로저 덕분에 다른 시점과 다른 위치에서 호출되더라도 실행환경을 유지할 수 있게됩니다.
물론 동기식으로 콜백을 사용하기도 합니다.
👻 동기와 비동기의 혼용은 굉장히 위험하다.
우리가 어떠한 함수를 작성하였고, 해당 함수에서 조건문에 따라 동기 혹은 비동기를 리턴한다고 생각해겠습니다.
우선 결론적으로 이러한 경우 어플리케이션을 손상시킬 수 있는 굉장히 위험한 방식입니다.
책에서는 아래와 같은 예제를 들어 이런 상황을 Zalgo를 풀어놓는 상황이라고 설명합니다.
파일을 처음 호출할 때는 비동기, 두번째는 동기식으로 동작합니다. 그렇기에 첫번째 호출에서만 이벤트리스너 등록할 시간이 있게되어
첫번째와 두번째 동작에 차이가 발생합니다.
import { readFile } from "fs"; const cache = new Map(); const inconsistentRead = (filename, cb) => { if (cache.has(filename)) cb(cache.get(filename)); else readFile(filename, "utf8", (err, data) => { cache.set(filename, data); cb(data); }); }; const createfileReader = (filename) => { const lsns = []; inconsistentRead(filename, (v) => { lsns.forEach((lsn) => lsn(v)); }); return { onDataReady: (lsn) => lsns.push(lsn) }; }; const reader1 = createfileReader("data.txt"); reader1.onDataReady((data) => { console.log(`first call data : ${data}`); const reader2 = createfileReader("data.txt"); reader2.onDataReady((data) => { console.log(`second call data : ${data}`); }); }); // first call data : data.txt
이러한 경우의 해결책은 당연하게도 2가지입니다. 아예 동기로 작성하거나 그 반대겠죠
동기로 작성해보게습니다. Node는 대부분의 I/O 작업에 대해서 동기식 스타일의 API를 제공한다고 하니 이를 사용하면 됩니다.
이때 주의할 점은 동기식으로 작성하였으니 더이상 CPS 방식으로 코드를 작성할 이유는 없어집니다.
이러한 동기식 방식의 문제점은 다들 아시겠지만 전체 어플리케이션의 속도를 떨어트립니다.
반대는 비동기로 작성하면 됩니다. 동기를 비동기로 바꾸는 가장 간단한 방법은 process.nextTick()을 통해 지연실행 시키는 겁니다.
웹 프론트에서 자주사용하는 requestAnimaionFrame과 동일하게 콜백을 인수로 취하여 대기 중인 I/O 이벤트 대기열에 삽입합니다.
책에서는 nextTick과 유사한 기능을 제공하는 setImmediate를 소개합니다.
setImmediate는 이벤트 큐의 맨뒤에 작업을 넣어주게 됨으로서 starvation을 일으킬수도 있다는 단점이 있습니다.
하나 더 소개하자면 setTimeout(cb, 0)과 setImmediate()에서 전자가 더 빠른 속도를 가집니다.
이유는 타이머의 경우에는 이벤트 큐와 별도로 동작하는 경우가 있으며 특히 크롬, 그렇지 않더라도 타이머가 setImmediate 보다 먼저
실행됩니다.
🚀 관찰자 패턴
비동기를 이해하는데 콜백과 함께 필수적인 관찰자 패턴에 대해서 알아보겠습니다.
많이 혼동하시는게 관찰자 패턴이라고해서 관찰자가 변화를 알아챈다는게 아니고 변화가 있을 때마다 직접 변화를 알리는 구조입니다.
이러한 점에서 콜백과 비교해보면 콜백은 오직 하나의 리스너에게 결과를 전달하고, 관찰자는 여러 리스너에세 전달하게 됩니다.
전통적으로 관찰자 패턴을 구현하려면 굉장히 골치가 아파오는데 node는 친절하게 EventEmitter 클래스로 구현되어 있습니다.
해당 클래스는 클라이언트 단에서 많이 사용하는 addEventListener와 비슷하다고 생각하면 쉽게 이해가실 겁니다.
EventEmitter는 하나 이상의 함수를 리스너로 등록 가능합니다.
책에서는 아래의 예제로 해당 클래스 사용법을 소개합니다. (일반적인 노드진영의 패턴은 아님 / 일반적인 패턴은 클래스 부모 상속받아서)
const findRegex = (files, regex) => { const emitter = new EventEmitter(); for (const file of files) { readFile(file, "utf8", (err, content) => { if (err) return emitter.emit("error", err); emitter.emit("fileread", file); const match = content.match(regex); if (match) match.forEach((el) => emitter.emit("found", file, el) ); }); } return emitter; }; findRegex(["fileA.txt", "fileB.json"], /hello \w+/g) .on("fileread", (file) => console.log(`${file} was read`)) .on("found", (file, match) => console.log(`matched ${match} in ${file}`) ) .on("error", (err) => console.log(`error emitted ${err.message}`) );
EventEmitter 사용할때 주의할 점은 리스너가 필요없어지면 구독을 해지해야한다는 점입니다.
구독해지는 removeListener() 메소드로 작성합니다.
🚀 콜백 패턴 vs 관찰자 패턴
비동기 api를 작성할 때 어떤 녀석을 사용할지 딜레마에 빠지게 됩니다.
이에 대한 명확한 답 또한 없습니다. 책에서는 몇가지 조언을 해줍니다.
- 콜백은 여러 유형의 결과를 전달하기에 제한이 있어 EventEmitter를 사용하면 군더더기 없는 코드 작성이 가능
- EventEmitter는 발생하지 않거나 여러번 발생하는 이벤트에 사용
🧑🏻💻 연습문제
1. 단순 이벤트
const findRegex = (files, regex) => { const emitter = new EventEmitter(); process.nextTick(() => emitter.emit("begin")); // 동기로 처리되기 때문에 동기로 emit하게되면 이벤트 리스너 등록전에 처리됩니다. // 다음 테스크로 위임하는 방식으로 처리합니다. for (const file of files) {} return emitter; };
2. Ticker
const ticker = (number, arg) => { const emitter = new EventEmitter(); const now = Date.now(); process.nextTick(() => { emitter.emit("begin"); if (now % 5 == 0) { emitter.emit("error"); } }); let timer; var tick = () => { emitter.emit("tick"); timer = setTimeout(tick, 50); }; tick(); setTimeout(() => { clearTimeout(timer); }, number); return emitter; }; ticker(100, "") .on("tick", (_) => console.log("tick")) .on("begin", (_) => console.log("begin")) .on("error", (_) => console.log("error"));
'JS' 카테고리의 다른 글
Node.JS 디자인 패턴 및 바이블 (4) (0) 2021.07.29 Node.JS 디자인 패턴 및 바이블 (2) (0) 2021.07.24 Node.JS 디자인 패턴 및 바이블 (1) (0) 2021.07.24 [Typescript] 1. 개론 (0) 2021.04.25 [Javascript] 6. 이벤트 처리 (0) 2021.04.25