자바스크립트는 ES6 이전에는 비동기 처리를 위해 콜백함수를 사용했다. 하지만 콜백 함수를 많이 사용하게 되면 콜백의 중첩으로 인한 복잡도가 증가하고, 비동기 함수를 중첩 시켜 사용하므로 에러, 예외 처리가 어렵다는 단점이 있었다. 그렇게 이러한 단점들을 해결하기 위해 프로미스가 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(3,4, (a_res) => {
console.log("A RESULT : ", a_res);
taskB(a_res, (b_res) => {
console.log("B RESULT : ", b_res);
taskC(b_res, (c_res) => {
console.log("C RESULT : ",c_res);
});
});
});
console.log("코드 끝");
위 코드는 콜백 함수를 이용한 비동기 함수 처리 코드이다.
이러한 콜백 함수 코드 형태는 함수가 중첩 될수록 들여쓰기의 수준이 깊어져 가독성을 떨어 트린다. 또한 코드의 흐름을 파악하기 어렵고, 함수 마다 에러의 예외 처리를 따로 해줘야하기 때문에 에러가 발생한 위치를 찾기 어렵다는 문제점이 있다.
위 코드를 프로미스를 활용하여 수정해보겠다.
function taskA(a,b) {
return new Promise((resolve) => {
setTimeout(() => {
const res = a + b;
resolve(res);
}, 3000)
})
}
function taskB(a) {
return new Promise((resolve) => {
setTimeout(() => {
const res = a * 2;
resolve(res);
}, 1000)
})
}
function taskC(a,cb) {
return new Promise((resolve) => {
setTimeout(() => {
const res = a * -1;
resolve(res);
}, 2000)
})
}
taskA(3,4)
.then(a_res => {
console.log("A RESULT : ", a_res);
return taskB(a_res);
})
.then(b_res => {
console.log("B RESULT : ", b_res);
return taskC(b_res);
})
.then(c_res => {
console.log("C RESULT : ", c_res);
})
console.log("코드 끝");
위 코드와 동일한 코드이다. 다만 콜백함수로 전달한 값을 프로미스 객체로 전달 했을 뿐이다. 이렇게 3개의 중첩 함수를 만나도 큰 차이를 보인다. 아래 코드는 코드의 흐름을 읽기 쉽다. 데이터가 어떻게 흘러가는지 쉽게 파악할 수 있다. 이렇게 프로미스를 활용하면 비동기 처리를 쉽게 할 수 있다.
또한 에러에 대한 예외 처리도 쉽다.
function promise(timer) {
return new Promise((resolve, reject) => {
// pending 상태
console.log('pending');
setTimeout(() => {
if(timer < 3000) {
// fulfilled 상태
resolve('fulfilled');
} else {
// reject 상태
reject('reject');
}
}, 1000)
})
}
Promise 객체의 콜백함수의 인자로는 resolve와 reject가 들어온다.
resolve를 실행하면 프로미스 객체의 상태는 fullfilled가 된다.
반대로 프로미스만 return하고 실행을 하지 않는다면 pending 상태이다.
거부는 reject 처리 되었을 때 나타난다.
function promise(timer) {
return new Promise((resolve, reject) => {
// pending 상태
console.log('pending');
setTimeout(() => {
if(timer < 3000) {
// fulfilled 상태
resolve('fulfilled');
} else {
// reject 상태
reject('reject');
}
}, 1000)
})
}
promise(2000)
.then(res => console.log(res)) // 'fulfilled' 출력
.catch(err => console.log(err)) // timer가 3000이상이면 예외처리로 실행 - 'reject' 출력
.finally(() => console.log('무조건 실행')) // finally는 무조건 실행된다.
프로미스 객체에는 3가지 핸들러를 사용할 수 있다.
.then() : 프로미스가 이행(fulfilled)되었을 때 실행할 콜백 함수를 등록하고, 새로운 프로미스를 반환한다..catch() : 프로미스가 거부(rejected) 되었을 때 실행할 콜백 함수를 등록하고, 새로운 프로미스를 반환한다..finally() : 프로미스가 이행되거나 거부될 때 상관없이 실행할 콜백 함수를 등록하고, 새로운 프로미스를 반환한다. (resolve, rejevt와 관계 없이 무조건 실행된다.)Promise의 특징 중 하나로, then을 나열하여 순차적으로 제어가 가능하다.
예를 들어 아래는 taskA 함수를 호출하여 프로미스를 생성하고, then 메서드를 통해 핸들러를 연결하는 과정을 보여준다. 이과정에서 프로미스 객체에 전달 받은 데이터를 매개변수로 return 하여 프로미스 객체로 만들어 다시 then 메서드를 통해 전달받은 데이터로 프로미스 객체를 실행한 결과를 전달 받는다. 위와 같은 과정을 반복하여 최종 값을 콘솔에 출력한다.
function taskA(a,b) {
return new Promise((resolve) => {
setTimeout(() => {
const res = a + b;
resolve(res);
}, 3000)
})
}
function taskB(a) {
return new Promise((resolve) => {
setTimeout(() => {
const res = a * 2;
resolve(res);
}, 1000)
})
}
function taskC(a,cb) {
return new Promise((resolve) => {
setTimeout(() => {
const res = a * -1;
resolve(res);
}, 2000)
})
}
taskA(3,4)
.then(a_res => {
// a_res : 7
console.log("A RESULT : ", a_res);
return taskB(a_res);
// taskB(7)이 호출되어 프로미스 객체로 반환된다.
})
.then(b_res => {
// B_res : 14
console.log("B RESULT : ", b_res);
return taskC(b_res);
// tascC(14)가 호출되어 프로미스 객체로 반환된다.
})
.then(c_res => {
// c_res : -14
console.log("C RESULT : ", c_res);
})
console.log("코드 끝");