React 게시글은 대부분 인프런의 '한입 크기로 잘라 먹는 리액트(React.js) : 기초부터 실전까지' 강의를 기반으로 내용을 정리했습니다.
Promise
동기와 비동기에 대해 포스팅했을 때 마지막에 콜백 지옥(callback hell)을 잠깐 얘기했었다. Promise에 대해 알아보기 전에 먼저 비동기 작업이 가질 수 있는 3가지 상태에 대해 알아보자.
비동기 작업이 가질 수 있는 3가지 상태
Pending
- 현재 비동기 작업이 진행중이거나, 현재 이 작업이 시작할 수 없는 문제가 발생했음을 의미한다.
Fulfilled
- 이행 또는 성공 상태로 이 비동기 작업이 우리가 의도한대로 정상적으로 완료된 상태를 의미한다.
Rejected
- 거부 또는 실패 상태로 비동기 작업이 모종의 이유(서버 비응답, 시간 지연 등)로 인해 실패했음을 의미한다.
resolve
- 대기 상태에서 성공 상태로 변화하는 과정을 resolve(해결)이라 한다.
reject
- 대기 상태에서 실패 상태로 변화하는 과정을 reject(거부)라고 한다.
resolve와 reject를 코드로 구현해보자.
2초 뒤에 비동기적으로 입력받은 값이 숫자인지 아닌지 판별하고, 만약 숫자면 양수와 음수인지 판별해주는 프로그램이다.
function isPositive(number, resolve, reject) {
setTimeout(() => {
if(typeof number === 'number') {
// 성공 -> resolve
resolve(number >=0? "양수" : "음수");
} else {
// 실패 -> reject
reject("주어진 값이 숫자형이 아닙니다.");
}
}, 2000)
}
isPositive("12", (res)=>{
console.log("성공적으로 수행됨 : " + res);
}, (err)=>{
console.log("실패하였음 : ", err) // 출력
});
위의 코드는 비동기, 콜백 함수를 이용해서 만든 기본적인 프로그램이다. 이번에는 Promise를 사용해서 코드를 작성해보자.
function isPositiveP(number) {
const executor = (resolve, reject) => {
setTimeout(()=>{
if(typeof number === 'number') {
// 성공 -> resolve
console.log(number); // 101 출력
resolve(number >=0? "양수" : "음수");
} else {
// 실패 -> reject
reject("주어진 값이 숫자형이 아닙니다.");
}
}, 2000);
};
};
isPositiveP(101);
Promise로 바꾸기 위해 새로운 함수를 만들었다. 아까 만든 코드와 다른 점은 executor 함수를 생성했다는 것이다. executor는 실행자. 즉 비동기 함수를 실질적으로 실행시키는 함수라고 일단 생각하면 된다.
function isPositiveP(number) {
const executor = (resolve, reject) => {
setTimeout(()=>{
if(typeof number === 'number') {
// 성공 -> resolve
console.log(number);
resolve(number >=0? "양수" : "음수");
} else {
// 실패 -> reject
reject("주어진 값이 숫자형이 아닙니다.");
}
}, 2000);
};
const asyncTask = new Promise(executor);
return asyncTask;
};
const res = isPositiveP(101);
Promise 사용을 위해 asyncTask Promise 타입 상수를 만들고, 그 상수를 return한다. isPositiveP()를 호출하면 이 return 값을 받기 위해 res라는 상수를 선언 후 호출하는 식으로 변경했다.
Promise 타입을 리턴하는 명령을 작성하면 함수의 설명에 Promise 객체를 반환한다고 업데이트된다.
이렇게 반환받은 Promise 객체를 사용하는 방법은 than과 catch를 사용하면 된다.
res.then((res)=>{
console.log("작업 성공 : " , res);
})
.catch((err) => {
console.log("작업 실패: ", err)
});
resolve를 수행한 결과 값을 then으로 가져올 수 있고, reject를 수행한 결과 값을 catch로 가져올 수 있다.
Promise를 이용해 콜백 지옥을 탈출하는 예제를 만들어보자. 저번 포스팅에 있었던 taskA, taskB, taskC 함수들!
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(4, 5, (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);
})
})
})
위 코드를 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)
})
}
const res = taskA(5, 1);
res.then((a_res)=> {
console.log("A result : ", a_res);
taskB(a_res).then((b_res) => {
console.log("B result : " , b_res);
taskC(b_res).then((c_res) => {
console.log("C result : ", c_res);
});
});
});
then을 이용해 콜백을 호출해봤는데....? 콜백 함수와 같은 then 지옥이 펼쳐졌다. 왜 이렇게 된거냐면 then을 일반 콜백함수 부르듯이 사용해서 그렇다. 호출 부분만 다시 변경해보자.
taskA(5, 1)
.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 reult : ", c_res);
});
이번엔 return문이 새로 생겼다. 쉽게 말하자면 그냥 a_res의 값을 taskB함수에 매개변수로 주면서 호출함과 동시에 taskA의 콜백은 종료된다. 이렇게 then을 연쇄적으로 사용하는 것을 then chaining이라고 한다. 나는 음 솔직히 일반적인 콜백 함수나 then chaining이랑 뭐가 다른지 모르겠다. 생김새만 다른 거 같은데....라고 하는 순간 새로운 설명이 추가 됐다.
Promise를 이용하면 비동기 처리를 호출하는 코드와 결과를 처리하는 코드를 분리해줄수 있는 장점이 있다.
const bPromiseResult = taskA(5, 1)
.then((a_res) => {
console.log("a result : ", a_res);
return taskB(a_res);
});
bPromiseResult.then((b_res) => {
console.log("b result : ", b_res);
return taskC(b_res);
})
.then((c_res) => {
console.log("c reult : ", c_res);
});
이런식으로.... 근데 아직 많이 안써봐서 그런지 뭐가 더 좋은건지 와닿지는 않는다.