비동기적 코드가 많아지면서 기하급수적으로 늘어나는 콜백 때문에
코드 전체의 로직을 가늠하기 어려운 경우가 많다.
이를 해결하기 위해 Promise가 등장했다.
💡 Promise는 콜백을 예측 가능한 패턴으로 사용할 수 있게 한다.
Promise 기반 비동기적 함수를 호출하면 그 함수는 Promise 인스턴스를 반환한다.
Promise는 성공(fulfilled) 하거나, 실패(rejected) 하거나 단 두가지 뿐이다.
즉, Promise는 성공 혹은 실패 둘 중 하나만 일어난다고 확신할 수 있다.
💡 성공, 혹은 실패로 결정된(settled) Promise 의 상태는 변할 수 없다.
const promiseInstance = new Promise((resolve, reject) => {
// executor 콜백 함수 내부
});
Promise 는 클래스이므로 new
키워드로 객체를 생성해야 한다.
생성자 함수의 인자로 executor 콜백 함수를 받는다.
executor 콜백 함수는 다시 resolve 콜백 함수와 reject 콜백 함수를 인자로 받는다.
💡 Promise의 인스턴스가 생성되는 순간 executor 함수 내부가 실행된다.
위에서 resolve와 reject가 executor 콜백 함수의 인자로 들어간 것을 떠올려보자.
resolve(값)
에 들어간 값
은 Promise의 인스턴스에서 then
메서드의 인자로 활용 가능하다.
예시를 보자.
const promiseInstance = new Promise((resolve, reject) => {
// pending..
// pending..
// pending..
resolve("goody"); // fulfilled
});
promiseInstance.then((result) => console.log(result));
// "goody"
💡 resolve의 인자로 들어간
"goody"
가promiseInstance.then
의 콜백함수에 인자로 들어간 것을 확인할 수 있다.
이번엔 executor 콜백 함수에 resolve 대신 reject를 넣어보자.
const promiseInstance = new Promise((resolve, reject) => {
reject(new Error("something wrong!")); // rejected
});
promiseInstance.catch((result) => console.error(result));
// Error: something wrong!
Promise 의 인스턴스에서 then 대신 catch 를 썼다는 것 빼고는 당장은 별다른 차이가 없어 보인다.
그러나 resolve와 reject는 Promise Chaining
에서 빛을 발한다.
처음에 봤던 코드를 다시 보자.
const promiseInstance = new Promise((resolve, reject) => {
resolve("goody"); // fulfilled
});
promiseInstance.then((result) => console.log(result));
// "goody"
// 반환 값: Promise (fulfilled)
promiseInstance
는 Promise 클래스의 객체이다.
따라서 Promise가 갖고있는 then, catch 등의 메서드를 사용할 수 있다.
그런데 then이나 catch는 또 다른 Promise 를 리턴하는 메서드이다.
그렇다면 Promise 뿐 만 아니라 then도 then을 가질 수 있다.
즉 then().then().then()
이런 식으로 체이닝이 가능하다는 이야기이다.
const workingPromise = new Promise((resolve, reject) => {
// executor 내부에는 통상 네트워크 요청 등 작업 시간이 오래 걸리는 비동기 로직이 들어가므로, 임의로 setTimeout을 넣었다.
setTimeout(() => resolve(1), 1000);
});
workingPromise
.then(result => result * 2)
.then(result => result * 3)
.then(result => {
console.log(result);
});
// 6
1 -> 1 * 2 -> 2 * 3 -> 6
executor 내부의 resolve()
에는 1
이 전달되었고,
체이닝을 거치며 최종 result
에는 6
이 저장되었다.
햄버거를 만드는 과정은 아래와 같다(고 가정하자).
🐮 데려오기
🥩 가공하기
🍔 ......완성!
🐮 없이는 🥩를 만들 수 없고, 🥩 없이는 🍔 를 만들 수 없다.
이를 Promise로 표현하면,
const getCow = () =>
new Promise((resolve, reject) => {
setTimeout(() => resolve(`🐮`), 1000); // 소를 데려오기 1초
});
const getMeat = (cow) =>
new Promise((resolve, reject) => {
setTimeout(() => resolve(`${cow} => 🥩`), 1000); // 도축 1초
});
const getBurger = (meat) =>
new Promise((resolve, reject) => {
setTimeout(() => resolve(`${meat} => 🍔`), 1000); // 햄버거 쌓기 1초
});
💡 getMeat 공정은 소를, getBurger 공정은 고기를 필요로 하고 있다.
당연한 소리겠지만, 햄버거를 만드려면 🐮를 데려오는 데에서 부터 시작해야한다.
공정의 순서는 아래와 같다.
getCow() // 소를 데려온다.
.then(result => getMeat(result)) // 데려온 result 를 고기로 만든다.
.then(result => getBurger(result)) // 고기가 된 result로 버거를 만든다.
.then(result => console.log(result)) // 결과를 출력한다.
// 결과 : 🐮 => 🥩 => 🍔
햄버거를 만드는 과정이 위처럼 순탄하기만 하면 좋겠지만,
getMeat
공정에서 에러가 발생했다고 해보자.
const getCow = () =>
new Promise((resolve, reject) => {
setTimeout(() => resolve(`🐮`), 1000);
});
const getMeat = (cow) =>
new Promise((resolve, reject) => {
setTimeout(() => reject(new Error (`error! ${cow} => 🥩`)), 1000);
});
const getBurger = (meat) =>
new Promise((resolve, reject) => {
setTimeout(() => resolve(`${meat} => 🍔`), 1000);
});
getCow() // 소를 데려온다.
.then(result => getMeat(result)) // 데려온 result 를 고기로 만든다.
.then(result => getBurger(result)) // 고기가 된 result로 버거를 만든다.
.then(result => console.log(result)) // 결과를 출력한다.
.catch(console.log)
// Error: error! 🐮 => 🥩
공정에 문제가 없었다면 햄버거가 완성되어야 하지만,
소에서 고기를 얻는 getMeat
함수에서 reject
가 발생했다.
이를 첫 번째 then
=> catch
에서 받아서 결과를 출력했으므로,
두 번째 then 함수 내부의 getBurger
가 실행되지 못한 것이다.
const getCow = () =>
new Promise((resolve, reject) => {
setTimeout(() => resolve(`🐮`), 1000); // 소를 데려오기 1초
});
const getMeat = (animal) =>
new Promise((resolve, reject) => {
setTimeout(() => {
if (animal === '🐮') resolve(`🥩`);
else reject(new Error(`error! ${animal} => 🥩`));
}, 1000) // 도축 1초
});
const getBurger = (meat) =>
new Promise((resolve, reject) => {
setTimeout(() => {
if (meat === '🥩') resolve(`🍔`);
else reject(new Error(`error! ${meat} => 🍔`));
}, 1000) // 햄버거 제조 1초
});
getCow() // 소를 데려온다.
.then(result => getMeat(result)) // 데려온 result 를 고기로 만든다.
.then(result => getBurger(result)) // 고기가 된 result로 버거를 만든다.
.then(result => console.log(result)) // 결과를 출력한다.
.catch(console.log)
animal 에 🐮 대신 🐷 가 들어간다면?
디버깅의 생명은 에러가 생긴 곳을 알아내는 것이다.
Promise 와 같은 비동기 함수는
코드의 흐름을 따라가기 쉽지 않으므로,
위처럼 에러처리를 분명하게 해주어야 시간을 절약할 수 있을 것이다.
드림코딩by 엘리 : https://www.youtube.com/watch?v=JB_yU6Oe2eE&t=1452s
러닝 자바스크립트 ES6 (이선 브라운 저)
정리 완전 깔끔하네요! 영상보고 글을 다시보니 더 분명해집니다!