목차
- Promise
- Promise 메서드
- then()
- catch()
- finally()
- Promise.all
- Promise.race
- Promise 체이닝
📌 Promise
: 비동기 작업을 처리하고 결과를 관리하는 데 사용되는 객체
let promise = new Promise(function(resolve, reject) {
executor
});
- 비동기 작업(네트워크 요청, 파일 읽기, 타이머 등)을 처리할 때 사용
- 성공하면 then(), 실패하면 catch() 실행
- 실제 promise에는 setTitmeout()이 필요 없음
(예시에서는 비동기를 구현하기 위해 사용됨)
👍 promise 장점
- 비동기 작업의 가독성 향상
- 콜백 지옥을 줄여줌 - 에러 처리의 일관성
- Promise 체이닝을 통해 체인의 어느 부분에서 발생한 에러든지 한 곳에서 처리할 수 있다. - 비동기 작업의 순차적 실행
- 비동기 작업을 처리하는 순서를 보장할 수 있다. - 다양한 비동기 작업 관리
- 여러 비동기 작업을 동시에 처리할 수 있는 메서드들을 제공한다.
Promise 생성
new Promise()
executor(실행자, 실행 함수)
: new Promise에 전달되는 함수
- executor는 new Promise가 만들어질 때 자동으로 실행됨
- 결과를 최종적으로 만들어내는 제작 코드를 포함함
- executor는 콜백 함수를 받는데 콜백 함수의 매개 변수가 함수(resolve, reject)로 정해져있다.
(변수명은 바꿀 수 있음⭕, but 순서(성공, 실패)는 지켜야 함❗)
- resolve(value)
: 일이 성공적으로 끝난 경우 그 결과를 나타내는 value와 함께 호출 - reject(error)
: 에러 발생 시 에러 객체를 나타내는 error와 함께 호출
- resolve(value)
promise 객체의 내부 프로퍼티
- new Promise 생성자가 반환하는 promise 객체는 내부 프로퍼티를 갖는다.
- state
- 처음엔 "pending"(보류)
- resolve가 호출되면 "fulfilled"
- reject가 호출되면 "rejected" - result
- 처음엔 "undefined"
- resolve가 호출되면 "value"
- reject가 호출되면 "error"
* Promise 값에는 접근 불가
const p = new Promise((resolve, reject)=>{
...
});
console.log(p.PromiseState); // X
📌 Promise 메서드
- then()
- catch()
- finally()
- Promise.all()
- Promise.race()
📍 then 메서드
promise 객체.then(
...
);
- Promise가 성공했을 때 실행될 콜백 함수(resolve)와 실패했을 때 실행될 콜백 함수(reject)를 인수로 받음
- Promise 체이닝 가능⭕
(또 다른 Promise를 반환하여 연속으로 사용) - 두 개의 콜백 함수를 인수로 받음
- 첫 번째 콜백 함수: Promise가 성공적으로 완료되었을 때(resolved) 실행
- 두 번째 콜백 함수: Promise가 실패했을 때(rejected) 실행
// 새로운 Promise 생성
let promise = new Promise(function(resolve, reject) {
// 비동기 작업을 시뮬레이션 (예: 서버 요청)
setTimeout(function() {
let success = true; // 성공 여부
if (success) {
resolve("작업이 성공했습니다!"); // 작업 성공 시
} else {
reject("작업이 실패했습니다."); // 작업 실패 시
}
}, 2000); // 2초 후에 실행
});
// then 메서드를 사용하여 결과 처리***
promise.then(
function(successMessage) { // 성공 시 실행
console.log(successMessage);
},
function(errorMessage) { // 실패 시 실행
console.log(errorMessage);
}
);
✨ then 메서드에서 return의 역할
- 비동기 작업을 체인으로 연결하고, 다음 then에서 이전 then의 결과 사용⭕
- return을 사용하면⭕, 다음 then에서 반환된 값을 사용할 수 있다.
(새로운 Promise 객체 반환) - return을 사용하지 않으면❌, undefined가 전달된다.
- then 메서드에서 명시적으로 값을 반환하면, 그 값이 다음 then 메서드의 PromiseResult 값이 된다❗
* 명시적으로 리턴값을 설정하는 경우
let promise = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(1); // 1초 후에 1 반환
}, 1000);
});
promise
.then(function(result) {
console.log(result); // 1 출력
return result * 2; // 2를 반환
})
.then(function(result) {
console.log(result); // 2 출력
return result * 3; // 6을 반환
})
.then(function(result) {
console.log(result); // 6 출력
});
명시적으로 값을 반환하지 않은 경우
let promise = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(1); // 1초 후에 1 반환
}, 1000);
});
promise
.then(function(result) {
console.log(result); // 1 출력
// 값을 반환하지 않음
})
.then(function(result) {
console.log(result); // undefined 출력
});
📍 catch 메서드
: Promise가 실패했을 때(rejected) 실행될 함수를 정의
- Promise then 메서드 체인에서 에러가 발생했을 때 그 에러를 잡아 처리하는데 사용
- 체인의 끝에서 사용하여 모든 이전 단계에서 발생한 에러를 한 번에 처리
- 체인의 중단 방지
- 에러가 발생하더라도 이후에 계속해서 then 메서드를 호출할 수 있도록 체인을 유지
let promise = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(1); // 1초 후에 1 반환
}, 1000);
});
promise
.then(function(result) {
console.log(result); // 1 출력
throw new Error("문제 발생!"); // 에러 발생
})
.catch(function(error) {
console.error("에러:", error.message); // "에러: 문제 발생!" 출력
return 2; // 새로운 값을 반환
})
.then(function(result) {
console.log(result); // 2 출력
});
// 첫 번째 then에서 에러를 던지지만, catch 메서드가 이 에러를 처리하고 새로운 값을 반환한다.
// 그 결과, 체인이 중단되지 않고 다음 then이 호출된다.
📍 finally 메서드
: Promise의 상태(resolve, reject)에 관계없이 무조건 실행되어야 하는 코드를 넣기 위해 사용
- Promise가 이행(fulfilled)되거나 거부(rejected)된 후에 항상 실행되는 코드를 작성할 때 사용
- 특정 작업이 완료된 후에 수행할 정리 작업(clean-up)을 실행할 때 유용
- 후속 작업(로딩 상태 해제, 자원 정리, 로그 기록 등)에 사용
finally 메서드의 특징
- 항상 실행
- finally 블록은 Promise가 이행되든 거부되든 무조건 실행된다. - 인수 없음
- finally 메서드의 콜백 함수는 인수를 받지 않음
즉, finally 블록 내부에서는 Promise의 결과나 에러에 접근할 수 없다❌ - 연결된 Promise 반환
- finally 메서드는 원래의 Promise를 반환함
따라서 finally 블록 이후에도 추가적인 then이나 catch 메서드를 체인으로 연결할 수 있다⭕
let promise = new Promise((resolve, reject) => { // 비동기 작업 수행
setTimeout(() => reject(new Error("실패!")), 1000);
});
promise
.then(result => {
console.log(result);
})
.catch(error => {
console.error(error); // "실패!" 출력
})
.finally(() => {
console.log("Promise가 완료되었습니다."); // 무조건 실행
});
// Promise가 거부되었기 때문에 catch 블록이 실행되고, 이후에 finally 블록이 실행됨
📍 Promise.all
- 연결된 Promise들이 모두 처리된 다음에 그 결과를 배열로 반환, then/catch 실행
- 모든 Promise들의 반환값들의 배열을 반환 (then의 매개변수 result)
- 여러 개의 Promise를 iterable 객체(ex 배열)에 담아서 실행하기 때문
const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = Promise.resolve(3);
Promise.all([promise1, promise2, promise3])
.then((results) => {
console.log(results); // [1, 2, 3]
})
.catch((error) => {
console.error(error); // 만약 하나라도 실패하면 이 블록이 실행됨
});
📍 Promise.race
- 가장 먼저 완료된 Promise의 결과만 반환 (then의 매개변수 result)
(Promise들이 경주해서 제일 빠른 것만 출력)
- 여러 개의 Promise를 iterable 객체(ex 배열)에 담아서 실행하기 때문
const promise1 = new Promise((resolve) => setTimeout(resolve, 500, 'first'));
const promise2 = new Promise((resolve) => setTimeout(resolve, 100, 'second'));
Promise.race([promise1, promise2])
.then((result) => {
console.log(result); // 'second' (promise2가 먼저 완료됨)
})
.catch((error) => {
console.error(error); // 실패한 경우 이 블록이 실행됨
});
📌 Promise 체이닝(promise chaining)
: 여러 비동기 작업을 순차적으로 실행하고, 각 작업이 완료된 후 다음 작업을 실행
- 각 프라미스가 완료될 때마다 .then() 메서드를 사용해 다음 프라미스를 반환
- 이렇게 하면 각 프라미스가 순차적으로 실행되고, 마지막 프라미스가 완료될 때까지 기다린다.
- Promise의 result가 .then() 핸들러의 연결을 통해 전달되는 점을 이용 - catch() 블록을 사용하여 체인의 모든 프라미스에서 발생한 에러를 한 곳에서 처리할 수 있다⭕
- 각 단계에서 결과를 받아 다음 단계에 전달할 수 있어, 비동기 작업 간의 의존성을 쉽게 관리할 수 있다⭕
// 첫 번째 비동기 작업을 수행하는 함수
function firstAsyncTask() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("첫 번째 작업 완료");
resolve("첫 번째 결과");
}, 1000);
});
}
// 두 번째 비동기 작업을 수행하는 함수
function secondAsyncTask(previousResult) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("두 번째 작업 완료: " + previousResult);
resolve("두 번째 결과");
}, 1000);
});
}
// 세 번째 비동기 작업을 수행하는 함수
function thirdAsyncTask(previousResult) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("세 번째 작업 완료: " + previousResult);
resolve("세 번째 결과");
}, 1000);
});
}
// 프라미스 체이닝 시작
firstAsyncTask()
.then(result => {
return secondAsyncTask(result);
})
.then(result => {
return thirdAsyncTask(result);
})
.then(result => {
console.log("모든 작업 완료: " + result);
})
.catch(error => {
console.error("에러 발생: " + error);
});
// firstAsyncTask() 함수가 호출되고 프라미스를 반환
// 첫 번째 프라미스가 완료되면 then 블록이 실행, secondAsyncTask 함수가 호출되어 새로운 프라미스를 반환
// 이때 firstAsyncTask의 결과가 secondAsyncTask에 전달
// 두 번째 프라미스가 완료되면 다시 then 블록이 실행, thirdAsyncTask 함수가 호출되어 또 다른 프라미스를 반환
// 이번에는 secondAsyncTask의 결과가 thirdAsyncTask에 전달
// 세 번째 프라미스가 완료되면 마지막 then 블록이 실행, 최종 결과 출력
// 만약 어느 프라미스에서든 에러가 발생하면, catch 블록이 실행되어 에러가 출력
예시 2
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
}).then(function(result) { // (*)
alert(result); // 1
return new Promise((resolve, reject) => {
setTimeout(() => resolve(result * 2), 1000);
});
}).then(function(result) { // (**)
alert(result); // 2
return new Promise((resolve, reject) => {
setTimeout(() => resolve(result * 2), 1000);
});
}).then(function(result) { // (***)
alert(result); // 4
});
// 1 2 4
// (*) 첫 번째 .then은 1을 출력하고 new Promise(…)를 반환((*))
// 1초 후 이 프라미스가 이행
// 그 결과(resolve의 인수인 result * 2)는 두 번째 .then((**))으로 전달
// (**) 두 번째 핸들러((**))는 2를 출력 new Promise(…)를 반환((**))
// 1초 후 이 프라미스가 이행
// 그 결과(resolve의 인수인 result * 2)는 세 번째 .then((***))으로 전달
📢 콜백 함수 vs Promise
콜백 함수 | Promise | |
코드 가독성 | 가독성👎 "콜백 지옥" ⛔ |
가독성 👍 유지보수가 쉬운 코드⭕ |
에러 처리 | 각 콜백 함수에서 에러를 처리해야 개별적으로 하므로 에러 처리가 분산, 일관성👎 | catch 메서드로 체인 전체의 에러를 일관되게 처리 👍 |
비동기 작업의 순차적 처리 | 중첩된 콜백 함수를 사용해야 해서 코드 가독성👎 |
then 메서드로 순차적으로 비동기 작업 처리⭕ |
여러 비동기 작업의 병렬 처리 | 병렬 처리를 위해서는 추가적인 논리와 구조가 필요 |
Promise.all과 Promise.race로 여러 비동기 작업을 쉽게 병렬 처리⭕ |