안녕하세요? 정리하는 개발자 워니즈입니다. 이번시간에는 javascript의 promise사용법에 대해서 정리릏 해보려고 합니다. 필자는 front-end에 굉장히 흥미를 많이 느끼고 있습니다. 화면에 바로바로 적용한 내용들이 노출되는데다가, 스크립트를 통해서 액션에 대한 동작을 하다보니까 확실히 재밌는 것 같습니다.
웹트렌드에서 굉장히 많이 활용했던 것이 jQuery였는데요. jQuery에서 제공해주는 여러가지 function을 통해서 dom을 제어하고 callback을 통해서 액션 이후에 여러가지 작업을 수행 할 수 있었습니다.
1. Callback 지옥에서 빠져나오기.
서두에 내용을 살짝 젂어뒀지만, jQuery를 쓰다보면, callback함수를 많이 쓰게 됩니다. 예를들어 button을 click하고 다음의 액션을 수행하는 정도의 액션은 손쉽게 javascript를 통해서 구현을 할 수 있습니다.
하지만, A부터 Z까지의 액션이 순차적으로 이루어지는 어플리케이션은 어떻게 구현일 될까요?
a(() => {
b(() => {
c(() => {
d(() => {
// and so on ...
});
});
});
});
위와 같이 중첩되는 경우가 바로 콜백 지옥입니다. a라는 작업을 수행하고, b,c,d까지 이어지는 흐름에서 코드 블록을 확인하기가 굉장히 어렵습니다. 위에서는 수도 코드로 나타내서 눈에 어느정도 보이지만, 코드량이 많아지고 복잡해지면 말그대로 콜백지옥에서 헤어나오기 쉽지가 않습니다.
이러한 상황을 해결해주는 것이 오늘 포스팅할 Promise라는 패턴입니다.
2. Promise란 무엇인가요?
싱글쓰레드인 자바스크립트에서 비동기 처리를 위해 콜백(callback)을 사용해 왔습니다. 위의 코드에서 보시다시피, 단점이 명확하게 드러나고 있습니다.
에러 처리가 어렵다.
중첩으로 인한 코드 가독성이 떨어지고, 복잡도가 증가
이러한 단점을 해결하기 위해서 프로미스의 개념이 등장했고, 이것을 ES6에서는 언어적 차원에서 지원하게 되었습니다.
위의 코드만 보더라도 훨씬 간결하고 명확해졌습니다. a,b,c,d를 순차적으로 수행한다. 에러가 발생하면 catch로 이동한다.
Promise는 말그대로 “약속” 입니다. 어떤 데이터를 요청하게 되면, 약속을 한 상황이기 때문에 지금 당장은 못주더라도 나중에 준다는 약속입니다.
약속에는 상태가 4가지 형태로 있습니다.
pending : 아직 약속을 수행 중인 상태입니다.
fulfilled : 약속이 지켜진 상태 입니다.
rejected : 약속이 어떤 이유에 의해서 거절 된 상태입니다.
settled : 최종적으로 약속 이후에 상태를 말합니다.
3. Promise 사용 패턴
Promise를 사용하는 방식은 다양하지만, 필자가 주로 사용하는 방식에 대해서 정리하도록 하겠습니다.
위에서 보는 것과 같이 promise에 대한 함수를 생성해 줍니다. 그리고 return으로 새로운 promise를 반환 하는 구조입니다. promise를 반환할 때, resolve,reject라는 파라미터를 받게 되고 실제로 약속 완료, 실패 여부에 대해서 함수를 호출하게 되면 최종적으로 완료되는 구조입니다.
호출부에서는 promise를 호출하는 순간 Promise객체가 생성이 됩니다. Promise는 비동기 작업이 완료되면 실행되는 then이라는 API가 존재합니다. 위의 예제는 Promise가 정상적으로 fulfilled된 이후에 then에 의해서 해당 파라미터(text)를 받아서 콘솔로 출력해주는 내용입니다.
4. Promise에서 에러 처리
then 함수를 통해서 chaining으로 연결을 계속해서 수행하다가 에러가 발생하게 되면 어떻게 처리를 하는지 정리하도록 하겠습니다. 위의 코드에서 에러 같은 경우 reject함수에 의해서 전달 되는 인자가 then함수의 2번재 function에서 실행이 되도록 합니다.
const requestCall = (siteCode, i) => {
return new Promise((resolve, reject) => {
const uri = siteCode === 'in' ? `https://ecom.${siteCode}.samsung.com/v4/configurator/syndicated-product` : `https://${siteCode}.ecom.samsung.com/v4/configurator/syndicated-product`;
const options = {
uri: uri,
method: 'POST',
body: JSON.stringify({
"skus": ["ALL"],
"offset": i,
"count": 50,
}),
encoding:null
};
request(options, (error, response, body) => {
try{
if (error) reject(error);
if (response.statusCode !== 200) {
console.log(response.statusCode);
let bodyStr = body.toString('utf-8');
console.log(bodyStr);
reject(`Failed with ${response.statusCode}`);
} else {
let bodyStr = body.toString('utf-8');
resolve(bodyStr);
}
}catch(e){
}
});
});
};
//0. 초기 호출 : maxIdx를 알아 낸 이후, 작업 시작.
const max = await FirstCallBulkApis(siteCode);
await callBulkApis(siteCode, 0, max);
외부 API를 호출하고, offset으로 총 몇페이지가 되는지를 계산을 합니다. 그리고 나서, 10개씩 호출을 수행하고, promise를 통해서 이후에 작업을 진행하는 내용입니다.
10개씩 일괄 호출 후, 작업
while문에서 curIndex = 0으로 시작하고, maxIdx는 페이지의 총 갯수입니다.
curIndex하위로 다시 whilte문이 돌면서 셋팅한 iteration(10개) 만큼 돌면서 array를 생성합니다.
이후에 request를 10개를 호출하게 되고, promise all을 통해서 모두 기다린다음에, 돌아온 json에 대해서 작업을 진행합니다.
const callBulkApis = (siteCode, thread, maxIdx) => {
return new Promise(async (resolve, reject) => {
try{
while(curIndex <= maxIdx){
let iterators = [];
let iteratorIndex = 0;
while(iteratorIndex < iteration){
if(curIndex + iteratorIndex >= maxIdx) break;
iterators.push(curIndex + iteratorIndex);
iteratorIndex++;
}
let requests = iterators.map(iterator => (requestCall(siteCode, iterator)));
await Promise.all(requests).then(function (values){
console.log(`${siteCode} 처리 진행중 : ${curIndex + iterators.length}/${maxIdx} 까지 처리 완료`);
//병렬 처리에 따른 pase부분 처리 변경
values.map((resultStr) => {
const resultJson = JSON.parse(resultStr);
//일괄 처리 작업
});
});
curIndex += iteration;
}
resolve();
}catch(e){
reject(e);
}
});
};
6. 마치며..
이전의 jQuery를 통해서 callback 지옥을 만드는것에서 벗어나서 이제는 코드가독성도 높이면서 ES6문법을 활용해서 좀더 효율적으로 코드를 작성했습니다. Promise는 잘만 활용하면 코드를 가독성있게 유지하고 이후에 유지보수하기도 훨신 편리한 것 같습니다.
다음에는 async와 await에 대해서 정리를 해보도록 하겠습니다.
code 상의 =>와 같은 기호들이 HTML 특수문자(>)로 처리된 것 같아요. 수정해주세요.
“const promise = (param) => {“
좋은 피드백 감사합니다! ㅎㅎ