단골 질문 Promise. 너 뭐 돼?

Deep Dive
2025년 09월 11일

들어가며

자바스크립트에서 비동기 작업의 결과를 이용해서 다음 작업을 수행하려면 어떻게 해야 하는가?

콜백 함수, Promise, async/await.. 굉장히 많은 키워드들이 뇌리를 스쳐지나간다.
그렇다면 이 키워드들은 어떤 차이를 가지고 있을까? 오늘은 이 질문에 대해 공부를 해보며 예제 코드와 함께 알아보려고 한다.

콜백함수란?

자바스크립트에는 콜백 함수라는 개념이 존재한다.

콜백 함수란 비동기 작업이 완료되면 호출되는 함수로, 비동기 함수의 매개변수로 함수 객체를 넘기는 기법을 의미한다. 따라서 비동기 함수 내부에서 함수 호출을 통해 비동기 작업의 결과를 받아서 인자로 념겨주며 이를 통해 동기처럼 작업을 수행할 수 있다.

하지만 콜백 함수를 사용하면 코드가 복잡해질 수 있으며 가독성이 떨어지는 문제가 발생한다. 예를 들어 아래 코드처럼 여러 개의 비동기 작업이 순차적으로 수행되어야 할 때 콜백 함수가 여러개 중첩되어 코드의 깊이가 깊어지는 콜백 지옥 현상이 발생할 수 있다.

function increase(count, callback) {
  setTimeout(() => {
    const increased = count + 1;
    console.log(increased);
    if (callback) {
      callback(increased);
    }
  }, 1000);
}
 
increase(0, count => {
  increase(count, count => {
    increase(count, count => {
      increase(count, count => {
        console.log('종료');
      });
    });
  });
});

Promise란?

프로미스는 자바스크립트의 객체로 비동기 작업의 완료 또는 실패를 나타내는 객체이다. 프로미스는 다음 중 하나의 상태를 가질 수 있다.

  • 대기(pending) : 초기 상태
  • 이행(fulfilled) : 연산이 성공적으로 완료됨
  • 거부(rejected) : 연산이 실패함

Promise로 비동기 처리하기

프로미스 객체는 new 키워드와 함께 Promise 생성자 함수를 이용하여 생성할 수 있다. 또한 프로미스 생성자의 매개변수로 두 개의 매개변수를 가진 콜백 함수를 넣을 수 있다. 매개변수는 각각 작업이 성공했을 때의 resolve 객체, 작업이 실패했을 때의 reject 객체이다.

const promise = new Promise((resolve, reject) => {
  // 비동기 작업
 
  if (성공) {
    resolve();
  } else {
    reject;
  }
});

우리가 자주 사용하는 fetch 메서드 또한 프로미스를 반환한다.
아래 예제는 https://jsonplaceholder.typicode.com/ 를 이용해서 작성했다.

function getUser() {
  return fetch('https://jsonplaceholder.typicode.com/todos/1').then(response => {
    if (!response.ok) {
      throw new Error('API 요청 실패');
    }
    return response.json();
  });
}
 
function getPost(userId) {
  return fetch(`https://jsonplaceholder.typicode.com/posts/${userId}`).then(response => {
    if (!response.ok) {
      throw new Error('API 요청 실패');
    }
    return response.json();
  });
}
 
function getComment(postId) {
  return fetch(`https://jsonplaceholder.typicode.com/posts/${postId}/comments`).then(response => {
    if (!response.ok) {
      throw new Error('API 요청 실패');
    }
    return response.json();
  });
}
 
getUser().then(({ userId }) =>
  getPost(userId)
    .then(({ id }) => getComment(id))
    .then(comment => console.log(comment))
);

이렇게 작성된 함수들을 프로미스 체인(then)을 이용하여 비동기 작업을 처리할 수 있다.

then() 함수는 새로운 프로미스를 반환한다.

async/await

async/await을 사용하면 프로미스를 더욱 편하게 사용할 수 있다.

async 함수

async 키워드를 function 앞에 위치시키면 해당 함수는 항상 프로미스를 반환한다. 만약에 함수 내부에서 강제로 프로미스가 아닌 값을 반환하게 하더라도 이행된 프로미스를 반환하도록 한다.

async function foo() {
  return 1;
}
 
foo().then(data => console.log(data)); // 1

await

awaitasync 함수 내부에서만 동작한다.
await 키워드를 만나면 프로미스가 처리될 때까지 기다리고 결과는 그 이후에 반환된다.

그렇다면 위에 프로미스로 작성한 코드를 async 함수들로 변경하면 아래와 같다.

async function getUser() {
  const response = await fetch(`https://jsonplaceholder.typicode.com/todos/1`);
 
  if (!response.ok) {
    throw new Error('API 요청 실패');
  }
 
  return response.json();
}
 
async function getPost(userId) {
  const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${userId}`);
 
  if (!response.ok) {
    throw new Error('API 요청 실패');
  }
 
  return response.json();
}
 
async function getComment(postId) {
  const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${postId}/comments`);
 
  if (!response.ok) {
    throw new Error('API 요청 실패');
  }
 
  return response.json();
}

이후 이 함수들을 순차적으로 실행시키기 위한 메인 함수도 하나 만들어준다.

async function main() {
  try {
    const userData = await getUser();
    const postData = await getPost(userData.id);
    const commentData = await getComment(postData.id);
 
    console.log(commentData);
  } catch (error) {
    console.error('Error: ', error);
  }
}
 
main();

위처럼 작성된 코드는 우리가 원하던 대로 비동기 코드이지만 순차적으로 실행되게 된다.

용어 정리

지금까지 학습한 내용을 간단하게 정리해보자.

Promise

프로미스란 비동기 작업의 완료 또는 실패를 나타내는 객체이다. 과거 비동기 작업을 콜백으로 처리했을 때 발생했던 콜백 지옥 문제와 중첩된 에러 핸들링 문제를 then 메서드를 활용해 프로미스 체이닝으로 해결할 수 있다. 또한 매개변수로 주어진 콜백은 주어진 순서대로 실행되는 것을 보장한다.

async/await

function 키워드 앞에 async 키워드를 붙여 async 함수를 생성할 수 있다. await 키워드는 반드시 async 함수 내부에서만 사용이 가능하며 try/catch 문을 통해 에러를 핸들링한다.

마무리

필자는 개발할 때 항상 사용하던 키워드들이였지만 갑자기 프로미스에 대해 설명해달라, async/await이랑 어떤게 달라? 라는 질문이 들어오면 대답을 못한 경험이 있었다. 이번 기회를 통해 간략하게나마 정리해 보았고 이 글을 읽는 독자들도 이 글을 통해 개념 정리와 더불어 더 깊게 프로미스에 대해 공부해볼 의지가 생기면 좋겠다는 마음을 전하며 마무리한다.