2장 (1/3): CPS 패턴

이 글은 CPS 패턴과 CPS가 Node.js에서 어떻게 사용되고, 어떤 점을 주의해야 하는지 다룬다.


1. CPS 패턴

Node.js는 1장에서 살펴봤듯 비동기 특성을 가지며, 따라서 Node.js 앱은 대부분의 일을 비동기로 처리할 수 밖에 없다. 비동기를 처리하는 방법 중 CPS, Continous Passing Style을 소개한다.

CPS: 비동기 API를 사용할 때, 콜백 함수를 인자로 넘기는 패턴이다.

  • 왜 사용하는가: 비동기 API는 return을 할 수 없는데, 함수의 실행이 끝나기 전에 제어권이 넘어가기 때문이다. 이를 해결하기 위해선 결과를 다른 함수에 넘기면 된다.
  • 장점: 간단하고 효과적이다.
  • 단점: 호출 깊이가 깊어지면 가독성이 감소된다. Callback Hell이라고 불린다.

2. Node.js에서의 CPS 패턴

Node.js는 CPS 패턴을 사용할 때 일관된 규칙을 따라야 한다.

  • argument 순서에 관한 규칙: (...params, callback) 과 같이, callback 함수를 마지막 인자로 넘겨야 한다.
  • callback 함수의 argument에 관한 규칙: (err, ...args) 와 같이, err가 첫 인자여야 한다.
  • err 인자의 경우, 항상 Error() 객체여야 한다. (이 부분은 잘 지켜지지 않는 듯 하다.)

3. CPS 패턴의 콜 스택

Node.js에서 비동기 API를 호출하는 경우, callback 함수는 프로그래머가 예상한 호출 순서로 구성된 스택을 갖지 않는다. 비동기 API가 완료됐을 때, 이벤트 루프에 의해 단일 함수로 Queue에 쌓인 후 다른 타이밍에 실행되기 때문에 새로운 스택에서 실행된다. 비동기 함수에서 예외를 던지면, Error를 반환하며 프로세스가 종료된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const fs = require('fs');
const readJsonThrows = (filename, cb) => {
try {
fs.readFile(filename, 'utf8', (err, data) => {
if (err) return cb(err);
cb(null, JSON.parse(data));
// cb이 없거나, data가 불량인 경우 exception 발생 가능.
// 콜백 함수 내에서 try-catch하지 않는 경우 프로세스가 죽는다.
});
} catch (err) {
// 여기서도 catch할 수 없음. 호출 스택은 fs.readFile에서 끝나고,
// cb은 별개의 새 스택에서 실행되기 때문
}
};

// 만약 JSON.parse에서 오류나는 경우, 프로세스가 종료된다.
readJsonThrows('C./test.json', (f) => f);
/*
SyntaxError: Unexcepted end of JSON input
at JSON.parse
at FSReqCallback.readFileAfterClose (internal/...)
*/

4. Node.js에서 비동기를 처리할 때 절대 하지 말아야 할 점들

1. 결괏값을 동기, 비동기 2가지 방식으로 전달하지 않는다.

  • 결괏값이 비동기일것을 기대하고 이벤트 리스너를 등록할 때, 동기로 결괏값이 제공되는 경우 이벤트 리스너가 동작하지 않는다.

  • 동기 반환값을 비동기화 한다. setTimeout, setImmediate, nextTick, Promise 등이 가능하다.

2. Callback 함수를 argument로 받는 동기 함수를 작성하지 않는다.

  • 동기 API는 바로 결괏값을 받는 형태로 코드를 작성하면 된다.

2장 (1/3): CPS 패턴

https://jsqna.com/ndp-2-cps/

Author

Seongbin Kim

Posted on

21-01-01

Updated on

21-01-19

Licensed under

댓글