Skip to main content

Command Palette

Search for a command to run...

Node.js 비동기 처리 이해하기: Promise 이전 방식부터 async/await, 병렬 실행까지

Published
3 min read

Node.js는 이벤트 루프 기반 구조로 비동기 I/O를 효율적으로 처리하는 런타임입니다.
비동기 처리 방식을 이해하면 Promise와 async/await의 차이뿐 아니라 병렬 실행 패턴까지 파악할 수 있습니다.
이 글에서는 초기 콜백 방식부터 현대적인 비동기 패턴까지 정리합니다.


1. Promise 이전 비동기 처리 방식

초기 Node.js는 콜백(callback) 방식을 중심으로 동작했습니다. 비동기 결과는 콜백 함수로 전달됩니다.

fs.readFile("data.txt", (err, data) => {
  if (err) {
    // 에러 처리
  }
  // 결과 사용
});

단순한 작업은 문제가 없지만, 여러 비동기 작업이 연속될 경우 콜백이 중첩되었습니다. 이른바 콜백 지옥(callback hell)입니다.

getUser(id, (err, user) => {
  getPosts(user.id, (err, posts) => {
    getComments(posts, (err, comments) => {
      save(comments, (err) => {
        // ...
      });
    });
  });
});

비즈니스 로직을 따라가기 어렵고, 에러 처리 흐름도 복잡해졌습니다. 이를 해결하기 위한 개념이 Promise입니다.


2. Promise의 등장과 역할

Promise는 “미래에 완료될 작업을 나타내는 객체”입니다.
비동기 상태를 관리할 수 있고, 체이닝을 통해 흐름을 구성할 수 있습니다.

getUser()
  .then(user => getPosts(user.id))
  .then(posts => save(posts))
  .catch(err => console.error(err));

콜백의 중첩을 줄이고, .catch()로 에러 처리 흐름을 일원화했습니다.
또한 Promise.all, Promise.race 등 여러 Promise를 조합하는 기능도 제공하여 병렬 처리까지 가능합니다.


3. async/await의 도입

async/await은 Promise 기반 문법으로, 비동기 작업을 동기 코드처럼 작성할 수 있습니다.

async function main() {
  try {
    const user = await getUser();
    const posts = await getPosts(user.id);
    await save(posts);
  } catch (err) {
    console.error(err);
  }
}

조건문, 반복문 등과 자연스럽게 결합할 수 있어 가독성이 크게 향상됩니다.
비즈니스 로직을 명확하게 표현할 수 있다는 점에서 Promise 체이닝보다 우수한 경우가 많습니다.


4. Promise와 async/await의 상호 변환 가능성

Promise → async/await

모든 Promise 기반 코드는 async/await으로 변환 가능합니다.
async/await 자체가 Promise 위에서 동작하기 때문입니다.

async/await → Promise

async/await 코드도 Promise 체이닝 형태로 변환 가능합니다.
다만, 로직이 복잡할 경우 가독성이 떨어지고 유지보수성이 저하될 수 있습니다.

정리하면, 두 방식은 서로 100% 변환 가능합니다.
async/await은 문법적 설탕(syntactic sugar)일 뿐이며, 내부 동작은 Promise 기반입니다.


5. 직렬 실행과 병렬 실행

비동기 코드라고 해서 모두 병렬로 실행되지는 않습니다.
await는 해당 Promise의 완료를 기다리기 때문에 코드가 자연스럽게 직렬 실행 형태를 띱니다.

직렬 실행 예시

await taskA();  // 1초
await taskB();  // 1초
await taskC();  // 1초

총 3초가 소요됩니다.
의존성이 있는 작업에 적합한 방식입니다.


병렬 실행 예시

const a = taskA();
const b = taskB();
const c = taskC();

await Promise.all([a, b, c]);

세 작업이 동시에 시작되며 총 1초만 소요됩니다.
서로 독립적인 작업일 때 효과적입니다.


비교 요약

구분직렬 실행병렬 실행
실행 순서순서대로 진행동시에 시작
총 실행 시간A + B + Cmax(A, B, C)
적합한 경우앞 결과가 필요한 의존 작업서로 독립적인 작업

병렬 실행을 하려면 Promise.all을 명시적으로 사용해야 합니다.
async/await은 기본적으로 직렬 처리 흐름을 만들기 때문입니다.


6. 마무리

Node.js 비동기 모델은 콜백에서 시작해 Promise를 거쳐 async/await으로 발전해 왔습니다.
세 방식은 모두 비동기 처리를 위한 기술이지만, 코드 복잡성과 유지보수성 측면에서 큰 차이가 있습니다.

특히 async/await가독성명료성이 뛰어나 현재 가장 널리 사용되는 방식입니다.
다만, 병렬 처리가 필요한 경우 Promise 조합 기능을 함께 사용해야 성능을 최적화할 수 있습니다.

Node.js 비동기 패턴의 흐름과 차이를 이해하면, 구조적이고 효율적인 서버 코드를 작성하는 데 도움이 됩니다.


🧾 작성 참고

이 글은 ChatGPT의 도움을 받아 내용을 정리하였습니다.

More from this blog

LLM은 어떻게 글을 읽고 쓰는가 — 입력부터 출력까지

대화형 AI에게 질문을 던지면, 마치 사람처럼 문장을 이해하고 답을 써 내려가는 것처럼 보인다.하지만 그 안에서 벌어지는 일은 생각보다 단순하고, 또 생각보다 기계적이다.이 글에서는 두 가지 질문에 답해 본다.LLM은 어떻게 글을 만들어내는가,그리고 우리가 입력한 프롬프트는 모델에 어떤 모습으로 들어가는가. 1부. LLM은 어떻게 글을 만들어내는가 핵심은

Jun 10, 20264 min read

useEffect 지옥이란 무엇이며, 어떻게 안전하게 사용하는가

React를 어느 정도 사용하다 보면 한 번쯤은“useEffect 지옥에 빠졌다”는 말을 듣거나 직접 느껴본 적이 있을 것이다. useEffect가 계속 늘어나고 의존성 배열은 점점 길어지고 왜 실행되는지 이해하기 어려워지며 eslint-disable-next-line 이 늘어나는 상태 하지만 많은 사람들이 오해한다.useEffect 지옥은 useEffect가 많아서 생기는 문제가 아니다. 이 글에서는 ‘useEffect 지옥’의 ...

Jan 12, 20263 min read

Zod란 무엇인가: TypeScript에서 런타임 검증과 타입 안정성을 동시에 해결하는 방법

TypeScript 프로젝트를 하다 보면 반드시 마주치는 문제가 있다.바로 “타입은 있는데, 데이터는 믿을 수 없다”는 것이다. TypeScript는 컴파일 타임에는 강력하지만,런타임에 들어오는 데이터에 대해서는 아무런 보장을 해주지 않는다. API 요청 데이터 사용자 입력(Form) 환경 변수 (process.env) 외부 JSON / 설정 파일 이 모든 것은 타입 시스템 바깥에서 들어온다. 이 문제를 해결하기 위해 등장한 라이브러...

Jan 10, 20263 min read

Flatpak을 이해하기 위한 배경 지식

배포판, 라이브러리 충돌, 그리고 OSTree까지 리눅스 데스크톱에서 Flatpak이 표준처럼 자리 잡은 데에는 분명한 이유가 있다.그 이유를 이해하려면 단순히 “Flatpak은 패키징 시스템이다”를 넘어서,리눅스 배포판 구조, 라이브러리 버전 충돌, OSTree라는 기술까지 함께 이해해야 한다. 이 글에서는 다음 질문에 답해본다. 리눅스에서 말하는 배포판이란 무엇인가? 라이브러리 버전 충돌은 왜 발생하는가? Flatpak은 이 문제를 어...

Jan 8, 20265 min read

npm SemVer 실무 가이드

이 글은 npm의 Semantic Versioning(SemVer) 을 처음 접하는 개발자부터, 실무에서 이미 사용하고 있지만 헷갈리는 포인트가 많은 개발자까지를 대상으로 한다. 단순한 규칙 나열이 아니라, 실제 업무에서 어떻게 쓰이고, 어디서 사고가 나며, 어떤 관습이 굳어졌는지를 중심으로 정리했다. 1. SemVer란 무엇인가? SemVer(Semantic Versioning)는 버전 번호에 의미를 부여하는 규칙이다. 기본 형식은 다음과 ...

Jan 7, 20263 min read

Dev note

31 posts