본문 바로가기

개발자 강화/프론트엔드

🌟[매일메일] 자바스크립트의 Promise란? (FE.250129/250203)

자바스크립트의 Promise

Promise 정의

비동기 작업을 관리하고, 작업의 성공/실패 결과를 나중에 사용할 수 있도록 하는 객체

비동기 작업의 완료 여부를 약속해주는 개념

 

Promise 역할

자바스크립트는 비동기 처리를 위한 콜백함수를 많이 사용함

코드가 복잡해져 콜백이 중첩되는 '콜백 지옥' 문제가 발생할 수 있음

 

Promise는 비동기 처리의 가독성을 높이고, 코드의 흐름을 명확하게 관리할 수 있도록 도와줌

 

Promise의 3가지 상태

1. Pending(대기): 비동기 작업이 아직 완료되지 않은 초기 상태

2. Fulfilled(이행됨): 비동기 작업이 성공적으로 완료되어 값을 반환한 상태

3. Rejected(거부됨): 비동기 작업이 실패해 오류를 반환한 상태

한 번 Fulfilled 또는 Rejected 상태로 변경되면 다시 다른 상태로 바뀌지 않음.

 


 

Promise의 기본 사용법

Promise 객체는 비동기 작업을 수행할 함수를 인자로 받아 실행함.

resolve()와 reject() 두 개의 콜백을 받음

const myPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    let success = true; // 성공 여부
    if (success) {
      resolve("🎉 작업 성공!");
    } else {
      reject("❌ 작업 실패!");
    }
  }, 2000);
});

// Promise 사용하기
myPromise
  .then((result) => {
    console.log(result); // 🎉 작업 성공!
  })
  .catch((error) => {
    console.error(error); // ❌ 작업 실패!
  });

resolve() 호출하면 fulfilled 상태로 변하고, reject()를 호출하면 rejected 상태가 됨.

.then()을 사용하면 성공 결과(resolve()된 값)를 받아 처리할 수 있음

.catch()을 사용하면 오류(reject()된 오류)를 받아 처리할 수 있음.

 

resolve(): Promise를 완료시키는 함수.

- Promise가 성공적으로 끝났을 때 결과 값을 넘겨주는 함수

- Promise가 실패하면 reject()가 호출되기 때문에, resolve() 호출 실패라는 개념은 존재하지 않음

 

fulfilled: 해당 Promise가 완료된 상태. 

- resolve() 호출 후 Promise의 상태는 fulfilled(이행됨)으로 바뀜

 

 

Promise 체이닝(Chaining)

then()을 연결해 여러 개의 비동기 작업을 순차적으로 실행할 수 있음

new Promise((resolve, reject) => {
  setTimeout(() => resolve(1), 1000); // 1초 후 1 반환
})
  .then((result) => {
    console.log(result); // 1
    return result * 2;
  })
  .then((result) => {
    console.log(result); // 2
    return result * 3;
  })
  .then((result) => {
    console.log(result); // 6
  });

Promise 체이닝은 순차적인 비동기 처리가 가능하게 하지만,

then()이 많아지면 가독성이 떨어질 수 있음

 

여러 개의 Promise 병렬 처리

비동기 작업 처리의 동시성을 위해 네 가지 정적 메서드를 제공함.

 

Promise.all()

모든 Promise가 성공하면 결과 배열을 반환하고, 하나라도 실패하면 전체가 실패함.

Promise.all([
  fetch("https://jsonplaceholder.typicode.com/todos/1"),
  fetch("https://jsonplaceholder.typicode.com/todos/2"),
])
  .then((responses) => Promise.all(responses.map((res) => res.json())))
  .then((data) => console.log(data))
  .catch((error) => console.error("에러 발생!", error));

 

Promise.allSettled()

모든 Promise의 성공실패 여부를 개별적으로 반환함

Promise.allSettled([
  Promise.resolve("성공!"),
  Promise.reject("실패!"),
  Promise.resolve("또 다른 성공!"),
]).then((results) => console.log(results));

 

Promise.any()

프로미스 중 하나라도 이행되면 이행하고, 모든 프로미스가 거부되면 거부함.

여러 Promise 중 가장 먼저 resolve 된 값만 반환함.

모든 Promise가 실패해야 catch가 실행됨.

const p1 = new Promise((resolve, reject) => setTimeout(reject, 100, "❌ p1 실패"));
const p2 = new Promise((resolve, reject) => setTimeout(resolve, 200, "✅ p2 성공"));
const p3 = new Promise((resolve, reject) => setTimeout(resolve, 300, "✅ p3 성공"));

Promise.any([p1, p2, p3])
  .then((result) => console.log("🎉 Promise.any 결과:", result)) 
  // p2가 가장 먼저 성공했으므로 -> 🎉 Promise.any 결과: ✅ p2 성공
  .catch((error) => console.error("❌ 에러 발생!", error));

 

Promise.race()

프로미스 중 하나라도 이행되면 이행되고, 프로미스 중 하나라도 거부되면 거부 됨.

const p1 = new Promise((resolve, reject) => setTimeout(reject, 100, "❌ p1 실패"));
const p2 = new Promise((resolve, reject) => setTimeout(resolve, 200, "✅ p2 성공"));
const p3 = new Promise((resolve, reject) => setTimeout(resolve, 300, "✅ p3 성공"));

Promise.race([p1, p2, p3])
  .then((result) => console.log("🎉 Promise.race 결과:", result)) 
  .catch((error) => console.error("❌ Promise.race 실패:", error));

Promise의 단점

1. 복잡한 에러 처리

단일 체인에서는 .catch()로 간단하게 에러를 처리할 수 있음.

그러나 여러 Promise가 중첩되거나 서로 다른 비동기 흐름에서 에러가 발생할 경우 복잡도가 증가함

 

2. 콜백 지옥을 완전히 해결하지 못함

then() 체인이 많아지면 중첩된 형태가 되고, 가독성이 떨어질 수 있음.

async/await로 개선할 수 있음. 비동기 코드를 동기처럼 읽을 수 있도록 개선함.

async function getTodo() {
  try {
    let response = await fetch("https://jsonplaceholder.typicode.com/todos/1");
    let data = await response.json(); // JSON 변환도 기다림
    console.log("📌 데이터 가져오기 성공:", data);
  } catch (error) {
    console.error("❌ 데이터 가져오기 실패:", error);
  }
}

getTodo();

async: 비동기 함수를 정의하는 키워드. 이 키워드가 붙은 함수는 기본적으로 항상 Promise 객체를 반환함.

await: 비동기 함수(Promise)가 완료될 때까지 기다려주는 역할. async 함수 내부에서만 사용할 수 있음.

try...catch 블록을 사용해 에러가 발생해도 catch 블록에서 쉽게 처리할 수 있음.

async function fetchMultipleTodos() {
  try {
    let response1 = await fetch("https://jsonplaceholder.typicode.com/todos/1");
    let data1 = await response1.json();
    console.log("📌 첫 번째 할 일:", data1);

    let response2 = await fetch("https://jsonplaceholder.typicode.com/todos/2");
    let data2 = await response2.json();
    console.log("📌 두 번째 할 일:", data2);
  } catch (error) {
    console.error("❌ 에러 발생!", error);
  }
}

fetchMultipleTodos();

then()을 연속적으로 사용하는 대신, await을 사용해 코드 흐름이 자연스럽고 가독성을 높임.

동기 코드처럼 실행 순서를 쉽게 파악할 수 있음.

async function fetchTodosInParallel() {
  try {
    let [response1, response2] = await Promise.all([
      fetch("https://jsonplaceholder.typicode.com/todos/1"),
      fetch("https://jsonplaceholder.typicode.com/todos/2"),
    ]);

    let data1 = await response1.json();
    let data2 = await response2.json();

    console.log("📌 병렬 처리 - 첫 번째 할 일:", data1);
    console.log("📌 병렬 처리 - 두 번째 할 일:", data2);
  } catch (error) {
    console.error("❌ 에러 발생!", error);
  }
}

fetchTodosInParallel();

Promise.all()을 사용하면 두 개의 fetch 요청이 동시에 실행되어 속도가 더 빨라짐.

순차적으로 실행할 필요가 없으면 Promise.all()을 활용하는 것이 더 효율적임.

 


 

출처

[1] 매일메일. 250129. 자바스크립트 Promise에 대해서 아는 대로 설명해주세요. 65번. https://maeil-mail.kr

[2] GPT에게 Promise에 대해 묻다

[3] mdn web docs. Promise. https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise 

[4] 매일메일 250203. Promise의 resolve()와 fulfilled에 대해서 설명해주세요. 73번. https://maeil-mail.kr