
자바스크립트 프로미스(Promise)는 비동기 작업을 처리하기 위한 객체이며,
비동기 작업을 보다 효과적으로 다룰 수 있도록 도와준다.
비동기 작업은 일반적으로 네트워크 요청이나 파일 읽기와 같이 시간이 걸리는 작업을 의미한다.
비동기적 방식을 처리하는 방법들을 사용하지 않는다면 콜백 함수의 과정이 끝나기 전에 다음 프로세스로 진행될 수 있다. 콜백 함수의 과정이 끝나기 전에 다음 프로세스로 넘어간다면 콜백 함수의 결과가 미처 넘어오지 못해 오류가 발생하거나 빈 화면이 뜰 수 있다. 이런 문제를 해결할 수 있는 것이 Promise이다. 그리고 콜백 지옥(Callback Hell)을 방지하고 가독성을 향상 시키며, 에러 처리가 용이하다.

프로미스는 Promise 생성자 함수를 통해 인스턴스화한다. Promise 생성자 함수는 비동기 작업을 수행할 콜백 함수를 인자로 전달받는데 이 콜백 함수는 resolve와 reject 함수를 인자로 전달받는다.
// Promise 객체의 생성
const promise = new Promise((resolve, reject) => {
// 비동기 작업을 수행한다.
if (/* 비동기 작업 수행 성공 */) {
resolve('result');
}
else { /* 비동기 작업 수행 실패 */
reject('failure reason');
}
});
Promise는 비동기 처리가 성공(fulfilled) 또는 실패(rejected)하였는지 등의 상태(state) 정보를 갖는다.
| 상태 | 의미 | 구현 |
|---|---|---|
| pending | 비동기 처리가 아직 수행되지 않은 상태(대기) | resolve 또는 reject 함수가 아직 호출되지 않은 상태 |
| fulfilled | 비동기 처리가 성공된 상태 (성공) | resolve 함수가 호출된 상태 |
| rejected | 비동기 처리가 실패된 상태 (실패) | reject 함수가 호출된 상태 |
Promise 생성자 함수가 인자로 전달받은 콜백 함수는 내부에서 비동기 처리 작업을 수행한다.
이때 비동기 작업을 성공하면 콜백 함수의 인자로 전달받은 resolve 함수를 호출한다. 이때 프로미스는 ‘fulfilled’ 상태가 된다. 비동기 작업을 실패하면 reject 함수를 호출한다. 이때 프로미스는 ‘rejected’ 상태가 된다.
프로미스가 생성되면, 그 작업은 이미 진행 중이고 언젠가는 성공하거나 실패할 것이다. 그 성공/실패 결과를 .then / .catch / .finally 메소드를 통해 받아 다음 후속 작업을 수행할 수 있다. 프로미스 메소드는 프로미스의 상태에 따라 체이닝 방식으로 호출되는 콜백 함수라고 보면 된다.
// 부적절한 URL이 지정되었기 때문에 에러가 발생한다.
fetch('https://jsonplaceholder.typicode.com/XXX/1')
.then(res => console.log(res), err => console.error(err)); // Error: 404
.then(res => console.xxx(res), err => console.error(err));
// 두 번째 콜백 함수는 첫 번째 콜백 함수에서 발생한 에러를 캐치하지 못한다.
.then() : 두 개의 콜백 함수를 인자로 전달 받는다.
첫 번째 콜백 함수는 성공시 호출되고 두 번째 함수는 실패시 호출된다.
단, then 메서드의 두 번째 콜백 함수는 첫 번째 콜백 함수에서 발생한 에러를
캐치하지 못하고 코드가 복잡해져서 가독성이 좋지 않다.
// 부적절한 URL이 지정되었기 때문에 에러가 발생한다.
fetch('https://jsonplaceholder.typicode.com/XXX/1')
.then(res => console.log(res))
.catch(err => console.error(err)); // Error: 404
.catch() : 예외(비동기 처리에서 발생한 에러와 then 메소드에서 발생한 에러)가 발생하면 호출된다.
// 부적절한 URL이 지정되었기 때문에 에러가 발생한다.
fetch('https://jsonplaceholder.typicode.com/XXX/1')
.then(res => console.log(res))
.catch(err => console.error(err)) // Error: 404
.finally(() =>console.log("end")); // 성공 실패와 상관없이 실행프로미스 체이닝은 여러 프로미스를 연결하여 비동기 작업을 순차적 또는 병렬로 처리하는 패턴입니다. 각 프로미스는 then 메소드를 통해 다음 프로미스와 연결되며, 이를 통해 코드를 간결하게 작성할 수 있습니다.
.finally() : 프로미스가 성공되거나 실패될 때 상관없이 실행할 콜백 함수를 등록하면 호출된다.
catch 메서드를 모든 then 메서드를 호출한 이후에 호출하면 비동기 처리에서 발생한 에러뿐만 아니라 then 메서드 내부에서 발생한 에러까지 모두 캐치할 수 있다. 또한 then 메서드에 두 번째 콜백 함수를 전달하는 것보다 catch 메서드를 사용하는 것이 가독성이 좋고 명확하다. 따라서 에러 처리는 then 메서드에서 하지 말고 catch 메서드를 사용하는 것을 권장한다.
프로미스 체이닝은 코드의 가독성을 높이고 콜백 지옥을 피하는데 유용하며,
여러 프로미스를 연결하여 비동기 작업을 순차적 또는 병렬로 처리하는 패턴이다.
각 프로미스는 then 메소드를 통해 다음 프로미스와 연결되며, 이를 통해 코드를 간결하게 작성할 수 있다.
// 비동기 함수를 반환하는 프로미스 예시
function asyncFunction(value) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(value);
}, 1000);
});
}
// 프로미스 체이닝
asyncFunction(1)
.then(result => {
console.log(result); // 1
return asyncFunction(result + 1);
})
.then(result => {
console.log(result); // 2
return asyncFunction(result * 2);
})
.then(result => {
console.log(result); // 4
})
.catch(error => {
console.error(error);
});
이 예시에서는 'asyncFunction' 이라는 비동기 함수를 사용하여 프로미스를 생성하고, then 메소드를 이용해 체이닝하는 방법을 보여준다. 각 then 메소드는 이전 프로미스의 결과를 받아 새로운 프로미스를 반환하고, 이를 통해 계속해서 다음 단계의 비동기 작업을 수행한다.
이런식으로 체이닝이 가능한 이유는 then 핸들러에서 값을 리턴하면, 그 반환값은 자동으로 프로미스 객체로 감싸져 반환되기 때문이다. 그리고 다음 then 핸들러에서 반환된 프로미스 객체를 받아 처리하는 것이다
만일 연결된 이행 핸들러에서 중간에 오류가 있는 처리를 행한다면 예외처리를 함으로써 catch 핸들러에 점프하도록 설정할 수 있다.
function asyncFunction(value) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (value === 2) {
reject(new Error("Error: Value is 2"));
} else {
resolve(value);
}
}, 1000);
});
}
// 프로미스 체이닝에서 중간에 오류 발생
asyncFunction(1)
.then(result => {
console.log(result); // 1
throw new Error("Error in the middle");// 중간에 오류를 발생시킴
})
.then(result => {
console.log(result); // 실행되지 않음
return asyncFunction(result * 2);
})
.catch(error => {
console.error(error.message); // Error in the middle
// catch 핸들러에서 예외 처리 후 계속해서 then 핸들러로 이어갈 수 있음
return "Handled error";
})
.then(result => {
console.log(result); // Handled error
});
만일 catch 핸들러 다음으로 then 핸들러가 이어서 체이닝 되어 있다면, 에러가 처리되고 가까운 then 핸들러로 제어 흐름이 넘어가 실행이 이어지게 된다.