자바스크립트의 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
'개발자 강화 > 프론트엔드' 카테고리의 다른 글
[매일메일] React의 메모이제이션? (FE.241223/250206) (1) | 2025.02.06 |
---|---|
[매일메일] Error Boundary란? (FE.250205) (3) | 2025.02.06 |
[매일메일] 클로저란? (FE.241212) (1) | 2025.02.01 |
[매일메일] 자바스크립트 함수의 특징 (FE.250101) (1) | 2025.01.31 |
[매일메일] 함수 선언식과 함수 표현식의 차이점? (FE.250131) (1) | 2025.01.31 |