JS Promise 개념 정리

dongEon·2025년 8월 21일
post-thumbnail

목차

  1. 콜백 함수란 무엇인가?
  2. 콜백 기반 비동기 처리의 한계
  3. Promise란 무엇인가?
  4. Promise의 상태와 흐름
  5. Promise의 생성 및 기본 사용법
  6. Promise 체이닝: then, catch, finally
  7. 주요 Promise 유틸 메서드들
  8. async/await 문법
  9. 콜백 기반 함수를 Promise로 변환하기
  10. 실전 예제 및 정리

1. 콜백 함수란 무엇인가?

📌 개념 설명

콜백 함수(Callback Function)란 다른 함수의 인자로 전달되어 실행되는 함수를 말합니다. 자바스크립트에서는 일급 객체(First-class Object)로서 함수를 값처럼 전달할 수 있기 때문에, 콜백 패턴은 아주 일반적으로 사용됩니다.

✍️ 예시

function greet(name, callback) {
  console.log("안녕하세요, " + name);
  callback();
}

function sayBye() {
  console.log("안녕히 가세요!");
}

greet("동언", sayBye);

위 코드에서 sayBye 함수는 greet 함수의 콜백으로 전달되어 나중에 실행됩니다.

🔍 역할

  • 로직의 흐름을 나중으로 미루고 싶을 때 (지연 실행)
  • 이벤트 기반 프로그래밍에 필수적 (예: 클릭 이벤트 후 실행)
  • 비동기 작업을 처리할 때 (파일 읽기, 서버 응답 처리 등)

🧠 주의할 점

  • 콜백 지옥(Callback Hell)로 인해 코드 가독성이 떨어질 수 있음
  • 에러 처리가 어렵고 중첩이 깊어짐

2. 콜백 기반 비동기 처리의 한계

📌 문제점 정리

콜백 함수는 간단한 비동기 흐름에서는 유용하지만, 여러 비동기 작업을 중첩해서 순차적으로 처리해야 할 경우 다음과 같은 문제들이 발생합니다.

😵 콜백 지옥 (Callback Hell)

readFile("user.json", (err, data) => {
  if (err) throw err;
  parseData(data, (err, result) => {
    if (err) throw err;
    fetchUser(result.id, (err, user) => {
      if (err) throw err;
      console.log(user);
    });
  });
});
  • 중첩 구조로 인해 가독성↓ 유지보수↓
  • 에러 처리 로직이 복잡하고 중복됨
  • 흐름 제어(순차, 병렬 처리)가 어려움

💡 이러한 단점을 해결하기 위해 등장한 것이 바로 Promise입니다.


3. Promise란 무엇인가?

📌 개념 설명

Promise는 자바스크립트의 비동기 처리를 위한 객체입니다. 비동기 작업의 최종 성공 또는 실패를 나타내는 값을 미래 시점에 전달하겠다는 약속을 의미합니다.

✅ Promise 상태

  • pending: 대기 중 (아직 결과가 없음)
  • fulfilled: 작업 성공
  • rejected: 작업 실패
const promise = new Promise((resolve, reject) => {
  // 비동기 로직
});

🤔 등장 배경

  • 콜백 지옥 → 가독성 문제
  • 순차적 흐름 처리 어려움
  • 에러 처리 로직의 분산

🧪 예시 코드

function fetchData(): Promise<string> {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const success = true;
      if (success) {
        resolve("🎉 데이터 로드 성공");
      } else {
        reject(new Error("❌ 데이터 로드 실패"));
      }
    }, 2000);
  });
}

📈 실전 활용

  • 서버 API 통신: fetch(), axios
  • setTimeout/setInterval 대체
  • Node.js 파일 처리 등

4. Promise의 상태와 흐름

🧱 세 가지 상태 & 설명

상태설명
pending초기 상태, 아직 결과가 없음
fulfilled비동기 작업이 성공적으로 완료됨
rejected비동기 작업이 실패하여 오류 발생

📈 상태 전이(transition)

  • resolve(value)fulfilled 상태로 전이
  • reject(error)rejected 상태로 전이
  • 한 번 상태가 정해지면 절대 바뀌지 않음 (immutable)
const promise = new Promise((resolve, reject) => {
  resolve("성공");
  reject("실패"); // 무시됨
});

5. Promise의 생성 및 기본 사용법

const promise = new Promise((resolve, reject) => {
  // 비동기 작업
  setTimeout(() => {
    const ok = true;
    if (ok) resolve("작업 완료");
    else reject(new Error("에러 발생"));
  }, 1000);
});
  • 📌 resolve(value) → 정상 결과 전달
  • 📌 reject(error) → 에러 객체 전달

6. Promise 체이닝: then, catch, finally

Promise의 가장 큰 장점 중 하나는 체이닝(Chaining) 을 통해 여러 비동기 작업을 순차적으로 연결할 수 있다는 점입니다.
then, catch
then()메서드는 성공 결과를, catch()메서드는 실패 결과를 처리합니다. 이 메서드들은 새로운 Promise를 반환하므로 계속해서 연결할 수 있습니다.

fetchData()
  .then((data) => {
    console.log("성공", data);
  })
  .catch((err) => {
    console.error("실패", err.message);
  });

🛠 finally

finally()메서드는 Promise의 성공/실패 여부와 상관없이 무조건 한 번 실행됩니다. 주로 로딩 스피너 제거 등 마무리 작업에 사용됩니다.

fetchData()
  .finally(() => {
    console.log("작업 종료");
  });

7. 주요 Promise 유틸 메서드들

7.1 Promise.all

  • 모든 Promise가 성공해야 결과 반환
  • 하나라도 실패하면 전체가 reject
Promise.all([fetchA(), fetchB(), fetchC()])
  .then(([a, b, c]) => console.log(a, b, c))
  .catch(console.error);

7.2 Promise.race

  • 가장 먼저 처리된 Promise의 결과 반환
Promise.race([fetchSlow(), fetchFast()])
  .then(console.log)
  .catch(console.error);

7.3 Promise.allSettled

  • 성공/실패 상관없이 각 결과 개별 확인
Promise.allSettled([fetch1(), fetch2()])
  .then((results) =>
    results.forEach((res) =>
      res.status === "fulfilled"
        ? console.log("성공:", res.value)
        : console.error("실패:", res.reason)
    )
  );

7.4 Promise.any

  • 가장 먼저 성공한 결과만 반환
  • 전부 실패하면 AggregateError 반환
Promise.any([fail1(), fail2(), success()])
  .then(console.log)
  .catch((e) => console.error("모두 실패", e));

8. async/await

📌 개념 설명

async/await는 Promise를 동기 코드처럼 사용할 수 있게 해주는 문법입니다.

  • async 함수는 항상 Promise를 반환합니다.
  • await은 해당 Promise의 결과가 나올 때까지 기다립니다.

🧪 예시 코드

async function loadUserProfile() {
  try {
    const name = await fetchName(); // Promise<string>
    const age = await fetchAge();   // Promise<number>
    const job = await fetchJob();   // Promise<string>

    return { name, age, job };
  } catch (err) {
    console.error("에러:", err);
    throw err;
  }
}

✅ 장점

장점설명
가독성 ↑중첩 없는 직관적 코드 작성 가능
에러 처리 용이try/catch 문법으로 일괄 처리 가능
디버깅 쉬움호출 스택 간결

⚠️ 주의사항

  • await는 반드시 async 함수 안에서 사용
  • 병렬 호출 시에는 Promise.all() 사용이 더 효율적

9. 콜백 기반 함수를 Promise로 변환하기

기존 콜백 패턴을 Promise로 감싸는 예제:

function legacy(callback: (value: string) => void) {
  setTimeout(() => {
    callback("✅ 완료");
  }, 1000);
}

function wrapped(): Promise<string> {
  return new Promise((resolve) => {
    legacy((result) => {
      resolve(result);
    });
  });
}

🧠 util.promisify() (Node.js 내장 유틸)도 유사한 기능을 제공합니다.


10. 실전 예제 및 정리

async function getWeatherInfo() {
  try {
    const [temp, wind] = await Promise.all([
      fetchTemperature(),
      fetchWindSpeed(),
    ]);
    return { temp, wind };
  } catch (err) {
    console.error("날씨 정보를 불러오는데 실패했습니다.");
    throw err;
  }
}

정리

  • 콜백 → Promise → async/await으로의 진화
  • 각각의 방식은 상황에 따라 적절히 선택
  • 병렬/순차 처리 유틸 메서드 적극 활용
profile
개발 중에 마주한 문제와 해결 과정, 새롭게 배운 지식, 그리고 알고리즘 문제 해결에 대한 다양한 인사이트를 공유하는 기술 블로그입니다

0개의 댓글