본문 바로가기
SW프로그래밍 개발/Javascript

Node.js 비동기 처리 개념 한 방에 끝내기 (콜백, 프로미스, async/await)

by ICT리더 리치 2025. 5. 12.
반응형

"콜백 지옥? 이제 그만!" Node.js 비동기 처리 흐름을 확실하게 정리하고 예제 코드로 한 번에 이해하세요.

안녕하세요! 오늘은 Node.js 초보자와 실무 입문자 분들을 위해 비동기 처리의 핵심 개념을 쉽고 빠르게 정리해드립니다. Node는 싱글 스레드 기반의 이벤트 루프 구조를 갖고 있어, 비동기 처리를 제대로 이해하지 않으면 디버깅부터 성능까지 큰 장애물이 됩니다. 이번 포스팅에서는 callbackPromiseasync/await 흐름을 단계별로 살펴보고, 실제 예제 코드로 바로 실습 가능한 구조로 정리했습니다.

프리미엄 카페에서 맥북과 커피를 앞에 두고 아이팟을 낀 채 집중하고 있는 20대 한국여성 개발자
따뜻한 분위기의 고급 카페에서 개발에 몰입하는 밝은 미소의 여성 개발자

1. 콜백 함수란? (Callback)

Node.js에서 콜백 함수는 비동기 작업이 끝났을 때 실행할 함수를 인자로 전달하는 방식입니다. 대표적으로 fs.readFile() 같은 파일 입출력 함수가 콜백을 사용합니다.

// 콜백 예시
const fs = require('fs');

fs.readFile('data.txt', 'utf8', function (err, data) {
  if (err) {
    console.error('파일 읽기 실패:', err);
    return;
  }
  console.log('파일 내용:', data);
});

단점은

콜백 중첩이 깊어질수록 코드가 복잡

해지는 이른바 콜백 지옥(callback hell)이 발생한다는 것입니다.

2. 프로미스(Promise)의 등장과 해결

Promise는 콜백 함수의 중첩 문제를 해결하고, 비동기 처리를 순차적으로 구성할 수 있게 만든 객체입니다. 성공(resolve)과 실패(reject)를 명확하게 구분하며, .then().catch()로 체이닝 처리가 가능합니다.

// 프로미스 예시
const fs = require('fs').promises;

fs.readFile('data.txt', 'utf8')
  .then(data => {
    console.log('파일 내용:', data);
  })
  .catch(err => {
    console.error('파일 읽기 실패:', err);
  });

Promise는 코드의 가독성과 유지보수를 크게 개선했지만, 여전히 .then().then() 체인 구조가 길어지면 가독성이 떨어질 수 있습니다.

3. async/await로 완성하는 가독성

ES2017(ES8)부터 도입된 async/await는 프로미스 기반 코드를 동기 코드처럼 깔끔하게 작성할 수 있도록 도와줍니다. await 키워드는 프로미스를 기다리는 동안

블로킹 없이 대기

하며, try/catch로 에러 핸들링도 직관적입니다.

// async/await 예시
const fs = require('fs').promises;

async function readFile() {
  try {
    const data = await fs.readFile('data.txt', 'utf8');
    console.log('파일 내용:', data);
  } catch (err) {
    console.error('파일 읽기 실패:', err);
  }
}

readFile();

코드의 흐름이 깔끔하고 명확하죠? 실무에서도 이제 대부분의 Node.js 비동기 처리는 async/await로 작성하는 것이 표준입니다.

4. 흐름 비교: 콜백 vs 프로미스 vs async/await

형식 장점 단점
콜백 간단한 로직에 적합 중첩 시 가독성 악화 (콜백 지옥)
Promise 체이닝 가능, 예외 처리 분리 .then() 연속 시 길어짐
async/await 동기 코드처럼 작성, try/catch 가능 에러 누락 주의, 예외 흐름 제어 필요

✅ 정리하자면, 콜백은 구시대 유산, Promise는 중간 단계, async/await은 현재 표준입니다.

Node.js 비동기 처리를 설명하는 한국어 인포그래픽과 노트북으로 코드를 작성하는 남성 개발자
콜백부터 async/await까지, Node.js 비동기 흐름을 한눈에 이해하는 실전형 인포그래픽

5. 비동기 에러 처리까지 완성하기

Node.js에서 비동기 함수의 예외 처리는 try/catch 또는 .catch()를 통해 분리해서 처리해야 합니다. 하지만 async 함수 바깥에서 발생한 예외는 놓치기 쉽기 때문에,

전역 에러 핸들링

도 병행해야 합니다.

// 비동기 예외 처리 예시
async function loadUser(id) {
  try {
    const user = await getUserFromDB(id);
    console.log('사용자:', user);
  } catch (err) {
    console.error('에러 발생:', err.message);
    // 예: DB 접속 실패, id 없음 등
  }
}

Node 앱에서는 다음과 같은 방식으로 예상치 못한 에러를 잡아 안정성을 높일 수 있습니다:

// 전역 에러 핸들링
process.on('unhandledRejection', (reason, promise) => {
  console.error('Unhandled Promise:', reason);
});

process.on('uncaughtException', (err) => {
  console.error('Uncaught Exception:', err);
});
  • try/catch는 함수 내부에서, 외부 로직은 전역 이벤트로 커버
  • 비동기 로직은 항상 실패 가능성을 전제로 코딩
  • 프로덕션 환경에서는 로그 시스템 연동 추천 (예: Winston, Sentry)

예외 처리까지 마무리되어야 진짜 “실무형 비동기 코드”가 됩니다.

6. 자주 묻는 질문 (FAQ)

Q Node.js는 왜 비동기 방식을 기본으로 하나요?

Node는 싱글 스레드 이벤트 루프 기반으로 설계되어, 블로킹 없이 많은 요청을 처리하기 위해 비동기 방식을 기본으로 채택했습니다.

Q 콜백 함수 대신 항상 async/await만 쓰면 되나요?

가능하지만, 일부 Node.js API나 외부 라이브러리는 여전히 콜백 기반이므로 Promise로 감싸거나 util.promisify() 등을 활용해야 합니다.

Q async 함수 내에서 여러 await을 병렬로 처리하려면?

Promise.all()을 사용하면 병렬로 여러 비동기 작업을 동시에 실행할 수 있습니다. 단, 하나라도 실패하면 전체가 reject됩니다.

Q async/await는 ES 몇 버전부터 지원되나요?

ES2017(ES8)부터 정식 지원되며, Node.js v7.6 이상에서 사용 가능합니다. 대부분의 최신 환경에서는 기본 지원됩니다.

Q await을 루프에서 사용할 때 주의할 점은?

forEach에서 await은 작동하지 않으므로, 반드시 for...of 또는 for 루프를 사용해야 순차 실행이 보장됩니다.

Node.js에서의 비동기 처리는 단순히 문법 숙지가 아니라, 비즈니스 로직의 흐름을 관리하는 핵심 기술입니다. 콜백에서 출발해 프로미스를 거쳐 async/await로 이어지는 진화는 가독성과 유지보수성을 위해 반드시 거쳐야 할 단계입니다.

이번 포스팅이 비동기 흐름에 대한 전체적인 그림을 이해하는 데 도움이 되었기를 바랍니다. 앞으로도 Node.js의 비동기 처리 방식은 API 통신, DB I/O, 파일 처리 등 모든 백엔드 영역에서 계속 쓰일 것이므로, 기초 개념을 확실히 다지는 것이 중요합니다.

비동기는 어렵지 않습니다. 제대로 한 번만 배우면, 실무에서 막힘없이 작성하는 자신을 발견하게 될 거예요! 🚀

반응형