Programming language/JavaScript

자바스크립트, Promise로 요청실패시 retry 처리하기

iKay 2019. 3. 22. 00:36
반응형

JavaScript로 코딩을 하다보면 I/O 연산은 논블로킹 방식으로 처리되기 때문에 비동기 처리를 반드시 적절하게 해야 한다. I/O함수에 콜백함수를 등록해 콜백함수 내에서 I/O 이후 로직을 처리하면 되지만 콜백이 중첩되다보면 콜백 지옥(callback hell[각주:1])에 빠지게 된다. 뿐만 아니라 콜백방식을 사용하게 되면 중첩된 콜백함수 중 하나에서 에러가 발생했을 때 예외 처리가 꽤 까다롭다. 


아래 순서도와 같이 데이터를 처리 한다고 가정해보자. 





콜백 방식으로 처리


위 순서도를 처리하기 위한 콜백 방식의 코드를 보자. 아래 코드는 남자의 키 데이터를 요청하고, 그 후 여자의 키 데이터를 요청한다. 이 때 여자의 키 데이터를 요청 후 응답은 50% 확률로 실패하고 실패하면 null을 반환한다고 가정하자.  


function requestBoysHeights (callback) {
  const heights = [175, 181, 165, 190, 166];
  setTimeout(()=> {
    callback(heights);
  }, 3*1000);
};


function requestGirlsHeights (callback) {
  const heights = 0.5 < Math.random()? [156, 164, 171, 160, 178]: null;
  setTimeout(()=> callback(heights), 1*1000);
};


function run (callback) {
  console.log('start');
  callback();
};


run( ()=> {
  requestBoysHeights((heights1) => {
    requestGirlsHeights((heights2) => {
    console.log('boys heights:', heights1);
    console.log('girls heights:', heights2);
    console.log('end');
    });
  });
});



실행결과이다. 50% 확률로 여자의 키 값을 null로 받게된다. 


start
boys heights: [ 175, 181, 165, 190, 166 ]
girls heights: null
end


이러한 콜백방식에서 여자의 키 데이터 값을 반드시 받기위해 실패시 재요청하고 싶은데 어떻게 처리하면 될까? 아래와 같이 retry 함수를 등록해 처리하면 된다.


function requestBoysHeights (callback) {
  const heights = [175, 181, 165, 190, 166];
  setTimeout(()=> callback(heights), 3*1000);
};


function requestGirlsHeights (callback) {
  const heights = 0.5 < Math.random()? [156, 164, 171, 160, 178]: null;
  setTimeout(()=> callback(heights), 1*1000);
};


function retry(n, requestFunction, callback) {
  requestFunction( heights => {
    if ( !heights ) { retry(n+1, requestFunction, callback); }
    else { callback(n, heights); }
});
}


function run (callback) {
  console.log('start');
  callback();
};


run( ()=> {
requestBoysHeights( (heights1) => {
  retry(0, requestGirlsHeights, (n, heights2)=> {
    console.log('retry cnt:', n);
    console.log('boys heights:', heights1);
    console.log('girls heights:', heights2);
    console.log('end');
  });
  });
});



결과, 


start
retry cnt: 1
boys heights: [ 175, 181, 165, 190, 166 ]
girls heights: [ 156, 164, 171, 160, 178 ]
end




Promise ?


Promise object는 비동기적 연산에 대한 성공, 실패 그리고 결과값을 나타낸다. [각주:2]


Promise는 ES6[각주:3] 부터 비동기처리를 위해 나오게 됐다. 내가 JavaScript를 좋아하는 이유는 ES6부터 Promise가 나온 것 처럼, 계속해서 JavaScript라는 언어는 코딩하기 좋은 방향으로 발전하고 있다고 생각하기 때문이다.  


Promise는 다음과 같이 세 가지의 상태가 있다.  


  • pending: 처음 상태로, fulfilled도 rejected도 되지 않은 상태
  • fulfilled: 연산이 성공적으로 수행된 상태
  • rejected: 연산이 실패한 상태


그리고 Promise.prototype.then( ), Promise.prototype.catch( ) 로 다음과 같이 체이닝 할 수 있다.







Promise 방식으로 처리



그렇다면 위 순서도의 내용을 Promise로 코딩하면 어떻게 될까?[각주:4] 


function requestBoysHeights (callback) {
  const heights = [175, 181, 165, 190, 166];
  setTimeout(()=> callback(heights), 3*1000);
}


function requestGirlsHeights () {
  return new Promise ((resolve, reject) => {
    const heights = 0.5 < Math.random()? [156, 164, 171, 160, 178]: null;
    setTimeout(()=> {
      if (heights) {
        resolve(heights);
      }
      reject(heights);
    }, 1*1000);
  });
}


function retry(n, promise) {
  return new Promise ((resolve, reject) => {
    promise()
        .then( heights => resolve([n, heights]))
        .catch( error => retry(n+1, promise).then(resolve).catch(reject));
      });
}


function run () {
  console.log('start');
  requestBoysHeights( height1 => {
    retry(0, requestGirlsHeights)
        .then((result) => {
          console.log('retry cnt:',result[0]);
          console.log('boys heights:', height1);
          console.log('girls heights:', result[1]);
        })
        .finally(()=> {
          console.log('end');
        });
  });
}


run();



위와 같이 pomise( ) 함수인 retry( ) 함수를 재귀적으로 처리하면 된다. 이 때, 반복요청 할 promise( ) 함수를 파라미터로 넘긴다면 다른 함수에서도 이 retry ( ) 함수를 사용할 수 있을 것이다. 


결과는 다음과 같다. 위 코드와 달리 retry 횟수를 추가했다.   


start
retry cnt: 4
boys heights: [ 175, 181, 165, 190, 166 ]
girls heights: [ 156, 164, 171, 160, 178 ]
end


run( )을 .then( ) 방식에서 async-await 방식으로 코딩하면 아래와 같을 것이다. 사실 내게는 async-await 방식을 사용해 try-catch 하는 것이 더 쉬운 것 같다. 비동기적 처리지만 동기적 처리 결과를 변수에 입력하는 것 처럼 보여서 말이다. 


function runAsync () {


  console.log('start');
  requestBoysHeights( async height1 => {
    try {
      const results = await retry(0, requestGirlsHeights);
      console.log('retry cnt:',results[0]);
      console.log('boys heights:', height1);
      console.log('girls heights:', results[1]);
    } finally {
       console.log('end');
    }


  });
}


runAsync();



이렇게 Promise로 요청실패시 retry하는 법을 정리해봤다. callback 방식이 Promise보다 예외처리가 어렵기 때문에 callback 방식으로 retry 하는 것이 어려울 줄 알았으나 생각보다 callback 방식으로 retry도 하는 것이 쉽다. callback 방식과 Promise의 예외처리 방식에 대해서는 다음에 따로 포스팅해봐야 겠다. 다음 번에는 Promise에서 유용한 Promise.all( ) 과 Promise.race( ) 메쏘드에 대해 위의 순서도 상황을 응용해 정리해야 겠다.   



  1. http://callbackhell.com/ [본문으로]
  2. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise [본문으로]
  3. http://exploringjs.com/es6/ [본문으로]
  4. https://dev.to/ycmjason/javascript-fetch-retry-upon-failure-3p6g [본문으로]
반응형