프로미스(Promise)에 대해 알아보기
Promise
비동기 작업을 처리하기 위한 특수한 객체인 Promise
는 기존의 콜백 함수에 대한 단점인 가독성, 에러 처리, 비동기 작업의 순차적 실행 등의 다양한 측면에서 우위를 가지고 있습니다.
Callback
Promise
에 대한 설명에 들어가기 전에 앞서 이야기한 기존 콜백 함수에 대한 단점에 대해서 이야기 해보겠습니다.
Callback hell
비동기 처리에서 여러개의 비동기 작업을 처리하기 위해서 아래와 같이 콜백 함수를 중첩해서 사용하면 마치 지옥과도 같은 콜백 헬(callback hell)
이 되어버립니다.
1
2
3
4
5
6
7
8
9
10
11
asyncFunctionA(function (valueA) {
asyncFunctionB(function (valueB) {
asyncFunctionC(function (valueC) {
asyncFunctionD(function (valueD) {
asyncFunctionE(function (valueE) {
// ...
});
});
});
});
});
마치 피라미드를 뒤집어 놓은 형태처럼 변하며 이렇게 되면 코드의 가독성이 떨어지고 유지보수가 힘들어질 수 있습니다.
에러 처리의 어려움
일반적인 경우에는 try-catch
문 등을 통하여 에러를 감지할 수 있지만 비동기 콜백의 경우 에러를 감지하지 못할 수 도 있습니다.
1
2
3
4
5
6
7
8
9
try {
setTimeout(function() {
throw new Error("Error");
}, 1000)
console.log('try');
} catch (e) {
console.log(e);
console.log('catch');
}
위의 코드를 예시로 보면 try 내부에서 1초후에 강제적으로 에러를 발생하게 끔 했는데 이것은 catch에서 감지되지 않습니다.
이유로는 비동기적으로 발생한 에러를 감지하지 못하기 때문입니다.
setTimeout 함수는 비동기적으로 동작하며, 지정된 시간(1000ms)이 지나면 콜백 함수가 호출됩니다. 따라서 setTimeout 함수가 호출되고 콜백 함수가 예약된 후, 바로 다음 줄로 넘어가는 것 입니다. 콜백 함수는 독립적으로 실행되므로, try-catch 문 바깥에서 발생한 에러는 catch 블록으로 잡히지 않습니다.
이렇게 되어 콜백 함수에서 발생한 에러는 이미 바깥으로 빠져나간 상태로 로그
와 외부에서의 에러문
만을 호출하는 것입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
try {
setTimeout(function() {
try {
throw new Error("Error");
} catch (e) {
console.log(e);
console.log('catch');
}
}, 1000);
console.log('try');
} catch (e) {
console.log(e);
}
만약에 setTimeout 함수 내부에서 오류를 감지하고 싶다면 위의 코드처럼 그 내부에서 try-catch문을 또 걸어야 할 것 입니다.
Promise 사용법
일반적으로 Promise
객체를 생성할 때는 new Promise()
구문을 사용하며, 콜백 함수를 인자로 받습니다. Promise는 resolve
와 reject
라는 두 개의 함수를 인자로 받으며 Promise 객체의 상태를 결정하는 역할을 합니다.
1
2
3
4
5
6
7
8
9
const promise = new Promise(function (resolve, reject) {
if (/* 조건문 */) {
/* 비동기 작업의 성공 */
resolve('success');
} else {
/* 비동기 작업의 실패 */
reject('error');
}
});
Promise의 상태
Promise
는 다음과 같은 3가지의 상태를 가질 수 있습니다.
대기(pending)
: 비동기 작업이 완료되지 않은 초기 상태이행(fulfilled)
: 비동기 작업이 성공적으로 완료된 상태로 결과 값이 존재거부(rejected)
: 비동기 작업이 실패한 상태로 실패 이유를 나타내는 에러가 존재
pending 상태
1
2
3
4
5
6
/* 아직 비동기 작업이 완료되지 않은 상태 */
const promise = new Promise((resolve, reject) => {
// ...
});
console.log(promise); // Promise { <pending> }
fulfilled 상태
이행된 상태로 예시 코드처럼 resolve
함수를 호출하여 Promise 객체를 fulfilled
상태로 변경한 후, then()
메서드를 통해 등록된 콜백 함수가 실행되어 결과 값을 받을 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
/* 비동기 작업이 성공적으로 완료된 상태 */
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Success');
}, 1000);
});
promise.then((result) => {
console.log(result); // Success
});
console.log(promise); // Promise { <pending> }
rejected 상태
거부된 상태로 예시 코드처럼 reject
함수를 호출하여 Promise 객체를 rejected
상태로 변경한 후, catch()
메서드를 통해 등록된 콜백 함수가 실행되어 에러 메시지를 출력합니다.
1
2
3
4
5
6
7
8
9
10
11
12
/* 비동기 작업이 실패한 상태 */
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('Failed'));
}, 1000);
});
promise.catch((error) => {
console.log(error.message); // Failed
});
console.log(promise); // Promise { <pending> }
아래는 프로미스의 흐름도로 Promise
객체의 비동기 작업에 대한 성공과 실패의 경우에 대한 일련의 과정을 보여줍니다.
인스턴스 메서드
Promise 객체에는 then
, catch
, finally
메서드가 존재하며, 이 3개의 메서드는 각각 다음과 같은 역할을 수행합니다.
then
then
은 Promise 객체가 성공적으로 완료되었을 때 실행할 콜백 함수를 등록하는 메서드로 2개의 인자를 가지고 있습니다.
작업이 성공적으로 완료되었을 때와 선택적으로 작업이 실패했을 때 실행할 콜백 함수입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* 에러가 발생 했을 때의 경우 */
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('Failed'));
}, 1000);
});
promise.then(
function (result) {
console.log('Success');
console.log(result);
},
function (err) {
console.log('Failed'); // Failed 로그 출력과 에러 메시지를 출력
console.log(err);
});
추가적으로 then
는 연속적으로 체인 형태로 호출할 수 있으며, 각각의 then
메서드는 새로운 Promise 객체를 반환합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* 체인 형태로 호출하는 경우 */
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Successfully");
}, 1000);
});
promise.then(function (result) {
console.log(result); // Successfully
return result;
}).then(function (result) {
console.log(result); // Successfully
});
catch
catch
는 Promise 객체에서 오류가 발생했을 때 실행할 콜백 함수를 등록하는 메서드로 단일 인자만 존재합니다. then
의 실패 콜백을 catch
로 대체할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
/* promise의 then의 onRejected를 catch로 대체한 예시 */
const promise = new Promise(function (resolve, reject) {
setTimeout(function () {
reject(new Error ("Error"));
}, 1000);
});
promise.then(function (result) {
console.log(result);
}).catch(function (error) {
console.log(error);
});
finally
finally
는Promise 객체의 성공 또는 실패와 관계없이 마지막으로 실행하기 위한 콜백 함수를 등록하는 메서드 입니다. then
과 catch
의 앞에 위치 할 수도 있지만 실행은 마지막에 진행됩니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const promise = new Promise(function (resolve, reject) {
const result = true;
if (result) {
resolve(result);
} else {
reject(new Error('Something went wrong'));
}
});
promise
.then(function(result) {
// 성공 시 실행되는 콜백 함수
console.log('Success:', result);
})
.catch(function(error) {
// 실패 시 실행되는 콜백 함수
console.error('Error:', error);
})
.finally(function() {
// 항상 실행되는 콜백 함수
console.log('Finally');
});
Reference
CAPTAIN PANGYO:자바스크립트 Promise 쉽게 이해하기
모던 자바스크립트 Deep Dive (자바스크립트의 기본 개념과 동작 원리)