Node.js 와 TypeScript 콜백(callback) 지옥 피하는 방법

Node.js는 Chrome의 V8 JavaScript 엔진을 기반으로 구축된 이벤트 중심의 non-blocking I/O 플랫폼입니다. 웹 서버를 포함하여 확장 가능한 네트워크 애플리케이션을 구축하는 데 널리 사용됩니다.

이러한 애플리케이션을 구축할 때 콜백 지옥(callback hell) 문제가 발생할 수 있습니다. 이는 여러 개의 중첩된 콜백이 있어 코드를 읽고 유지 관리하기 어렵게 만드는 문제를 의미합니다.

이 글에서는 Express 웹 프레임워크와 TypeScript를 사용하여 Node.js 애플리케이션을 구축할 때 콜백 지옥을 피하는 방법에 대해 설명합니다.

Promises

Promises 은 보다 읽기 쉽고 관리하기 쉬운 방식으로 비동기 작업을 처리하는 방법입니다. 이는 아직 사용할 수 없지만 미래에는 사용할 수 있는 값을 나타냅니다. Promise를 서로 연결하면 여러 비동기 작업을 순차적으로 처리할 수 있습니다.

Node.js에서 Promise를 사용하는 예는 다음과 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function getData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Data received!');
}, 1000);
});
}

getData()
.then((data) => {
console.log(data);
})
.catch((error) => {
console.error(error);
});

이 예에서 getData 는 1초 후에 해결되는 약속을 반환합니다. 그런 다음 .then 메서드를 사용하여 확인된 값을 처리하고 .catch 메서드를 사용하여 오류를 처리할 수 있습니다.

Async/Await

Async/await 는 Promise 위에 추가되는 Syntatic Sugar(문법 설탕) 으로, 비동기 작업 작업을 더 쉽게 만듭니다. 이를 통해 동기적으로 보이는 비동기 코드를 작성할 수 있으므로 읽고 유지하기가 더 쉬워집니다.

컴퓨터 과학에서 Syntactic sugar(구문 설탕)은 더 쉽게 읽거나 표현하도록 설계된 프로그래밍 언어 내의 구문입니다. 그것은 인간이 사용하기에 언어를 "더 달콤"하게 만듭니다. 사물을 더 명확하고 간결하게 표현하거나 일부 사람들이 선호하는 대체 스타일로 표현할 수 있습니다. 위키백과

Node.js에서 async/await를 사용하는 예는 다음과 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function getData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Data received!');
}, 1000);
});
}

async function main() {
try {
const data = await getData();
console.log(data);
} catch (error) {
console.error(error);
}
}

main();

이 예에서 mainwait 키워드를 사용하여 getData 함수의 확인된 값을 기다리는 비동기 함수입니다. try/catch 블록을 사용하여 오류를 처리할 수 있습니다.

Promisify

Promisify는 callback-based 함수를 promise-based 함수로 변환할 수 있도록 Node.js에서 제공하는 유틸리티 함수입니다. 이는 Promise를 사용하지 않는 오래된 라이브러리나 모듈로 작업할 때 유용할 수 있습니다.

Node.js에서 promisify를 사용하는 예는 다음과 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const fs = require('fs');
const { promisify } = require('util');

const readFile = promisify(fs.readFile);

async function main() {
try {
const data = await readFile('file.txt', 'utf8');
console.log(data);
} catch (error) {
console.error(error);
}
}

main();

예에서는 util.promisify 를 사용하여 Node.js의 fs.readFile 함수를 promise-based 함수로 변환합니다. 그런 다음 Promise 기반 함수를 사용하여 file.txt 파일의 내용을 읽습니다.

Use Control Flow Libraries

복잡한 비동기 코드로 작업하는 경우 Promise와 async/await 만으로는 코드를 깔끔하고 유지 관리하기 쉽도록 유지하는 데 충분하지 않다는 것을 알 수 있습니다. 이 경우 제어 흐름(flow library) 라이브러리를 사용하여 비동기 코드를 관리하는 데 도움을 받을 수 있습니다.

제어 흐름 라이브러리는 비동기 작업의 흐름을 관리하는 방법을 제공하여 해당 작업이 올바른 순서로 실행되고 오류가 적절하게 처리되도록 합니다. Node.js의 인기 있는 제어 흐름 라이브러리로는 Async, Bluebird 및 Q가 있습니다.

다음은 Async 라이브러리를 사용하여 일련의 비동기 작업을 관리하는 방법에 대한 예입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const async = require('async');
const fs = require('fs');

async.series(
[
(callback) => {
fs.readFile('file1.txt', 'utf8', callback);
},
(callback) => {
fs.readFile('file2.txt', 'utf8', callback);
},
(callback) => {
fs.readFile('file3.txt', 'utf8', callback);
},
],
(error, results) => {
if (error) {
console.error(error);
} else {
console.log(results);
}
}
);

이 예에서는 async.series 메서드를 사용하여 일련의 비동기 작업(이 경우 세 파일의 내용 읽기)을 순서대로 실행합니다. 모든 작업이 완료되면 최종 콜백이 호출되고 결과 배열을 받습니다.

결론

콜백 지옥은 Node.js에서 비동기 코드 작업을 어렵고 실망스럽게 만들 수 있지만, 이를 방지하기 위해 사용할 수 있는 많은 전략은 위과 같습니다.

Share