본문 바로가기

언어/Node.js

[ Node.js ] 비동기 함수들의 장점과 단점 정리

반응형

Node.js에서 비동기 프로그래밍을 구현하는 방법인 콜백, 프라미스, async/await, 네이티브 비동기 방식의 장점과 단점을 살펴보겠습니다.

1. 콜백 (Callback)

장점:

  • 간단함: 사용법이 단순하며, JavaScript 초창기부터 사용되어 널리 알려져 있습니다.
  • 효율적: 메모리 사용이 적고, 이벤트 루프의 성능을 최대한 활용합니다.
  • 보편성: 많은 Node.js API가 콜백 기반으로 설계되어 있어 직접 사용하거나 이해하기 쉽습니다.

단점:

  • 콜백 헬: 중첩된 콜백이 많아질 경우 코드가 난해해지고 가독성이 떨어집니다. 이를 "콜백 지옥(callback hell)" 또는 "피라미드 코드(pyramid of doom)"라고 합니다.
  • 에러 처리의 어려움: 에러 처리가 각 콜백 내에서 이루어져야 하므로 코드가 더 복잡해질 수 있습니다.
  • 유연성 부족: 여러 비동기 작업을 조합하거나 연결하는 것이 어렵습니다.
// 콜백 헬의 예
fs.readFile('file1.txt', 'utf8', (err, data1) => {
    if (err) throw err;
    fs.readFile('file2.txt', 'utf8', (err, data2) => {
        if (err) throw err;
        fs.readFile('file3.txt', 'utf8', (err, data3) => {
            if (err) throw err;
            console.log(data1, data2, data3);
        });
    });
});

2. 프라미스 (Promise)

장점:

  • 가독성 향상: 비동기 작업을 체인(chain) 형태로 연결할 수 있어 코드가 더 읽기 쉽고 관리하기 쉬워집니다.
  • 에러 전파: catch 블록을 사용하여 프로미스 체인의 모든 에러를 한 곳에서 처리할 수 있습니다.
  • 유연성: Promise.all, Promise.race와 같은 메서드를 사용하여 병렬 또는 경쟁적으로 비동기 작업을 처리할 수 있습니다.

단점:

  • 구문이 복잡할 수 있음: 복잡한 프로미스 체인은 여전히 이해하기 어려울 수 있습니다.
  • 초기 학습 곡선: 콜백에 비해 이해하기 어렵다고 느낄 수 있으며, 초기 학습이 필요합니다.
// Promise를 사용한 예
const fs = require('fs').promises;

Promise.all([
    fs.readFile('file1.txt', 'utf8'),
    fs.readFile('file2.txt', 'utf8'),
    fs.readFile('file3.txt', 'utf8')
])
.then(([data1, data2, data3]) => {
    console.log(data1, data2, data3);
})
.catch(err => {
    console.error('Error reading files:', err);
});

3. async/await

장점:

  • 가독성: 동기 코드와 유사하게 작성할 수 있어 읽기 쉽고 이해하기 쉽습니다.
  • 에러 처리: try/catch 블록을 사용하여 직관적으로 에러를 처리할 수 있습니다.
  • 코드 구조 단순화: 복잡한 비동기 흐름을 더 쉽게 관리할 수 있습니다.

단점:

  • 약간의 성능 저하: async/await는 프로미스를 사용하므로 약간의 성능 오버헤드가 있을 수 있습니다.
  • ES2017 이후: async/await는 ES2017에 도입되었기 때문에, 이전 버전의 Node.js에서는 사용할 수 없습니다.
  • 병렬 처리가 아닌 직렬 처리: 기본적으로 await는 직렬 처리를 하므로, 병렬로 작업을 처리하려면 별도의 코드가 필요합니다.
// async/await를 사용한 예
const fs = require('fs').promises;

async function readFiles() {
    try {
        const [data1, data2, data3] = await Promise.all([
            fs.readFile('file1.txt', 'utf8'),
            fs.readFile('file2.txt', 'utf8'),
            fs.readFile('file3.txt', 'utf8')
        ]);
        console.log(data1, data2, data3);
    } catch (err) {
        console.error('Error reading files:', err);
    }
}

readFiles();

4. 네이티브 비동기 API (Native Async APIs)

장점:

  • 최적화: Node.js에서 기본 제공하는 비동기 API는 내부적으로 최적화되어 있어 효율적으로 동작합니다.
  • 간단한 사용: 콜백을 간단하게 추가하여 사용할 수 있습니다. 비동기 작업에 적합한 많은 네이티브 API가 제공됩니다.
  • API 지원: Node.js의 대부분의 핵심 모듈이 비동기 API를 제공하여 쉽게 사용할 수 있습니다.

단점:

  • 콜백 기반: 여전히 콜백 기반이므로, 콜백 헬로 이어질 수 있습니다.
  • 단일 접근 방식: 비동기 작업을 관리하거나 조합하는 데 있어 유연성이 부족할 수 있습니다.
const fs = require('fs');

// 비동기 파일 읽기
fs.readFile('example.txt', 'utf8', (err, data) => {
    if (err) {
        console.error('Error reading file:', err);
        return;
    }
    console.log('File content:', data);
});

요약

  • 콜백은 단순하지만, 콜백 헬과 에러 처리가 어렵습니다.
  • 프라미스는 가독성과 에러 처리를 개선하지만, 구문이 복잡할 수 있습니다.
  • async/await은 가장 가독성이 좋고, 직관적인 에러 처리를 제공하지만, 성능 오버헤드와 병렬 처리를 따로 고려해야 합니다.
  • 네이티브 비동기 API는 최적화되어 있고 간단하게 사용할 수 있지만, 콜백 기반으로 복잡한 비동기 흐름 관리에 한계가 있을 수 있습니다.

Node.js에서 비동기 작업을 처리할 때는 이들 방식의 장단점을 고려하여 상황에 맞는 방법을 선택하는 것이 중요합니다.

반응형