콜백지옥에서 벗어나기#
지난 포스팅에서 Callback함수란 무엇인가에 대해서 다뤘고, 복잡한 로직에서 Callback함수를 사용할때의 문제점, 콜백지옥에 대해서도 다뤘습니다.
콜백지옥을 짧게 요약하자면 다음과 같습니다.
- 코드의 가독성이 떨어진다.
- 매 코드블럭들마다 에러처리를 해주어야 한다.
Promise?#
어떤 특정한 문법이 아니라 일종의 패턴을 나타내는 용어였던 Callback
과 달리 Promise
는 Javascript의 객체입니다.
간단히 말하자면 비동기로 실행된 작업의 결과를 나타내는 객체의 이름이라고 보시면 됩니다.
Promise 만들기!#
간단히 아래와 같이 Promise
생성자로 만들 수 있습니다.
let promise = new Promise(function(resolve, reject) {
// Doing Something!
});
Promise
객체는 파라미터로 executor
라는 함수를 받아서 비동기로 실행하고, 성공이든 실패든 값을 반환하게 됩니다.
executor
는 두개의 Callback 파라미터를 갖고 있습니다.
resolve(value)
: 성공시, value를 반환하는 함수 호출reject(error)
: 실패시, error를 반환하는 함수 호출
Promise의 4가지 상태#
성공과 실패가 있으니 당연히 Promise라는 객체가 갖고있는 상태가 있겠죠?
크게는 성공(fulfilled
)과 실패(rejected
)가 있고, 실행중인 상태(pending
)과 실행이 끝났을 때(settled
)가 있습니다.
조금 더 자세히 설명하자면,
pending
:resolve(value)
또는reject(error)
가 호출되기 전, 실행중인 상태fulfilled
:resolve(value)
가 호출된 상태. 성공적으로 실행.rejected
:reject(error)
가 호출된 상태. 실행중 실패.settled
: 성공/실패 상태유무와 별개로 모든 프로세스가 종료된 상태.
아래 예시코드에서는 Promise
생성자로 새로운 Promise
를 만들어 성공적으로 일을 마쳤다는 것을 가정해 resolve
함수를 호출하고 있습니다.
let p = new Promise(function(resolve, reject) {
// Doing something!
resolve(1);
});
console.log(p);
그렇게 반환된 Promise객체는 변수 “p"에 저장되고, 그것을 출력해보면
Promise {[[PromiseState]]: 'fulfilled', [[PromiseResult]]: 1}
상태는 fulfilled
, 반환된 값은 resolve
함수에 넣었던 “1"이 담겨있습니다.
이번엔 작업이 실패했음을 가정해서 reject
함수를 호출해보겠습니다.
let p = new Promise(function(resolve, reject) {
// Doing something!
reject("ERROR!!!!!!!");
});
console.log(p);
Promise {[[PromiseState]]: 'rejected', [[PromiseResult]]: 'ERROR!!!!!!!'}
이번엔 상태가 rejected
로 뜨고 reject
함수에 넣었던 에러메세지가 담겨있습니다!
then, catch, finally#
그런데 사실 Promise로 실행된 값을 정상적으로 받아보려면 console.log
가 아니라 다른 방법을 사용해야 합니다.
왜냐면 Promise는 비동기작업이기 때문이죠!
이전 Callback함수에서 체인을 만들어 비동기작업의 후속작업을 확정적으로 실행할 수 있게 했듯이,Promise
도 작업들을 연결해주는 메서드가 있습니다.
then#
then
은 이전 Promise
객체를 받아서 처리해주는 구문입니다.
let p = new Promise(function(resolve, reject) {
// Doing something!
});
p.then(
res => {/* promise resolved */},
err => {/* promise rejected */}
);
then
은 두개의 Callback함수를 인자로 받습니다.
- 첫번째 인자는 promise가 정상적으로 실행되어 값을 반환한 경우 (resolved)
- 두번째 인자는 promise가 비정상적으로 실행되어 에러값을 반환한 경우(rejected)
예제로 보면:
let p = new Promise(function(resolve, reject) {
// Doing something!
resolve(1);
});
p.then(
res => {
/* promise resolved */
console.log(res); //1
},
err => {/* promise rejected */}
);
첫번째 인자를 받아서 출력했더니, Promise
에서 정상적으로 실행되었다고 가정하여 호출된 resolve
함수에 담긴 값이 출력되었습니다.
이번엔 실패하였다고 가정하여 error를 출력해보겠습니다.
let p = new Promise(function(resolve, reject) {
// Doing something!
reject("ERROR!!!");
});
p.then(
res => {
/* promise resolved */
console.log(res);
},
err => {
/* promise rejected */
console.log(err); // ERROR!!!
}
);
reject
함수에 담겼던 값이 then
의 두번째 콜백함수에서 출력되는 것을 확인할 수 있습니다.
만약 성공적으로 처리한 경우만 다루고 싶다면, then
에 첫번째 인자만 정의해주면 됩니다.
p.then(
res => {
/* promise resolved */
console.log(res);
}
);
생략도 가능합니다.
p.then(
/* promise resolved */
console.log
);
catch#
catch
는 에러를 처리해주는 부분입니다.
try-catch문을 생각하면 이해가 쉬울 것 같습니다.
let p = new Promise(function(resolve, reject) {
// Doing something!
reject("Error!!!");
});
p.then(
res => {console.log(1,res)}
).then(
res => {console.log(2,res)}
).catch(
err => {console.log(err)} //Error!!!
);
finally#
finally
는 Promise
가 성공했든 실패했든 마무리가 지어지면 실행되는 부분입니다.
try-catch-finally문을 생각할 수도 있는데 약간 다릅니다.
let p = new Promise(function(resolve, reject) {
// Doing something!
resolve(1);
});
p.then(
res => {console.log(res);} //1
).catch(
err => {console.log(err);}
).finally(
() => {console.log("finally!!")} //finally!!
);
이렇게 보면 then
과 catch
가 전부 끝난 후 finally
가 실행되는 것처럼 보이지만,finally
는 Promise
의 프로세스가 마무리된 후 실행되는 부분이기 때문에 어느 위치든 Promise
이후면 실행됩니다.
let p = new Promise(function(resolve, reject) {
// Doing something!
resolve(1);
});
p.finally(
() => {console.log("finally!!")} //finally!!
).then(
res => {console.log(res);} //1
)
위 예시처럼 then
이 끝나야 finally
가 실행되는게 아니라 Promise
가 종료되고나서 finally
가 실행되고, 결과값은 finally
를 통과해서 then
으로 넘어가게 됩니다.