ES6에서 도입된 프로미스에 대해 간략히 정리하는 글이다.
자바스크립트는 비동기 처리 결과값을 상위 스코프로 가져갈 수 없기 때문에 비동기 처리를 하는 함수 내부에서 로직을 구현하거나 콜백 함수를 이용하여 결과값을 전달한다.
function taskA(a, b, cb){
setTimeout(() => {
const res = a + b;
cb(res);
}, 3000)
}
function taskB(a, cb){
setTimeout(() => {
const res = a * 2;
cb(res);
}, 1000)
}
function taskC(a, cb){
setTimeout(() => {
const res = a * -1;
cb(res);
}, 2000)
}
taskA(2, 3, (a_res) => {
console.log("A RESULT: "+ a_res) // A RESULT: 5
taskB(a_res, (b_res) => {
console.log("B RESULT: "+ b_res) // B RESULT: 10
taskC(b_res, (c_res) =>{
console.log("C RESULT: "+ c_res) // C RESULT: -10
})
})
})
위의 코드를 보면 setTimeout 함수를 이용하여 비동기로 동작하는 함수 3개가 있다. taskA 함수의 결과값을 콜백함수로 전달하여 taskB 함수의 인자로 넘겨주었고 같은 패턴의 작업이 아래로 반복되고 있다. 위와 같은 패턴이 반복되면 자칫 콜백 헬로 이어질 수 있고 가독성이 안좋을 뿐만 아니라 setTimeout 함수의 콜백함수에서 발생한 에러는 try...catch에서도 잡히지 않기 때문에 상당히 번거롭다.
위와 같은 문제를 극복하기 위해 ES6에서는 비동기 처리를 위한 다른 패턴으로 프로미스를 도입했다.
//Promise 생성
const promise = new Promise((resolve, reject) => {
if(/* 비동기 처리 성공 */){
resolve("result");
}else {/* 비동기 처리 실패 */
reject("failure");
};
});
promise.then((res) => {console.log(res)}).catch((rej) => {console.log(rej)});
위 코드와 같이 프로미스를 생성할 수 있는데 프로미스 생성자 함수가 인수로 전달받은 콜백 함수 내부에서 비동기 처리를 수행한다.
이때 비동기 처리 성공시 인수로 전달받은 resolve 함수를 호출하고 실패시 reject 함수를 호출한다.
생성된 프로미스는 then을 사용하여 비동기 처리가 성공했을때 호출되는 콜백함수인 resolve를 인수로 받아 후속 처리가 가능하고 catch를 사용하여 비동기 처리가 실패했을때 호출되는 콜백함수인 reject를 인수로 받아 에러처리가 가능하다. 이때 then과 catch의 콜백함수 모두 프로미스로 부터 결과값을 인수로 받아 처리한다.
처음 작성하였던 코드를 Promise로 바꾸어보면 아래와 같다.
function taskA(a, b) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const res = a + b;
resolve(res);
}, 3000);
});
}
function taskB(a) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const res = a * 2;
resolve(res);
}, 1000);
});
}
function taskC(a) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const res = a * -1;
resolve(res);
}, 2000);
});
}
taskA(2, 3)
.then((a_res) => {
console.log("taskA: " + a_res); //taskA: 5
return taskB(a_res);
})
.then((b_res) => {
console.log("taskB: " + b_res); //taskB: 10
return taskC(b_res);
})
.then((c_res) => {
console.log("taskC: " + c_res); //taskC: -10
});
가독성이 훨씬 좋아지기도 했고 setTimeout에서 혹시라도 에러가 발생한다면 함수 내부에서 분기하여 에러발생시 reject함수를 호출하고 catch로 외부에서도 에러처리가 훨씬 용이할 것 같다. 위의 코드는 아래와 같이도 사용이 가능하다.
const bPromiseResult = taskA(2, 3).then((a_res) => {
console.log("taskA: " + a_res); //taskA: 5
return taskB(a_res);
});
// 다른 로직 코드 ...
//
//
//
bPromiseResult
.then((b_res) => {
console.log("taskB: " + b_res); //taskB: 10
return taskC(b_res);
})
.then((c_res) => {
console.log("taskC: " + c_res); //taskC: -10
});
bPromiseResult의 return값이 프로미스이기 때문에 변수에 담아서 다른 로직을 실행하고 난 후에도 후속처리가 가능한 장점이 있다.