Javascript Promise(프로미스) 만들기


**목차** \- Promises와 Asynchronous? \- Callback을 이용한 비동기 및 Callback Hell \- 프로미스 용어 \- Syntax 및 Promise 생성 방법 \- 프로미스를 XHR과 사용 \- 프로미스를 Fetch API와 사용
##### Promises와 Asynchronous? 웹개발을 잘 하기 위해서는 비동기 프로그래밍에 능숙해야 합니다. 비동기 처리를 위한 여러 방법이 있고 promise도 그 중 한가지입니다. 다음은 MDN에 나와있는 Promise 소개에 대한 한 부분입니다.

"A Promise is an object representing the eventual completion or failure of an asynchronous operation."

Promise는 비동기 프로그래밍에서 미래의 어느 시점에 실행할 코드를 나타내는 객체입니다. 현실에서 비유를 하자면 은행 업무를 위해 번호표를 뽑고 기다리는 것과 비슷합니다. 번호표가 Promise이고 창구로 가서 도움을 받는 행위를 callback으로 간주할 수 있습니다.

Promises를 이용하면 비동기 처리를 위한 코드를 더 읽기 쉽게 하고, 유지 보수하기에도 더 편하게 작업할 수 있습니다. 프로미스는 비동기적으로 작동하는 코드를 try-catch 방식으로 감싸 진행하는 ES6의 기능입니다.

일반적으로 자바스크립트 코드는 동기적입니다. 다시 말해서, 하나의 코드가 실행을 마치면, 순차적으로 그다음의 코드가 실행이 됩니다. 반면 비동기적 코드는 코드가 정확히 언제 완료될지 확신할 수 없습니다. 아래의 코드를 살펴보겠습니다.

Synchronous

const second = () => {
  console.log('2');
};

const first = () => {
  console.log('1');
  second();
  console.log('3');
};
first(); // 1, 2, 3 이 출력됨.

Asynchoronous

const second = () => {
  setTimeout(() => {
    console.log('2');
  }, 2000);
};

const first = () => {
  console.log('1');
  second();
  console.log('3');
};
first();
// setTimeout이 끝나길 기다리지 않고, 1,3 출력 후 2가 출력이됨.

##### Callback을 이용한 비동기 및 Callback Hell 비동기를 구현하기 위해 함수 내에서, 그 함수의 결과값으로 또 다른 작업을 하기 위해 callback을 연속적으로 사용하는 경우가 있습니다. 함수 내부의 callback 함수가 많아질수록, 코드 읽기와 유지 보수가 힘들어집니다. 또한 함수 단위로 나누어 관리하기가 어렵습니다.
##### 프로미스 용어 프로미스를 이용하기 위한 기본적인 용어는 아래와 같습니다. \- fulfilled(resolved) : 프로미스가 성공적으로 이루어짐. \- rejected : 프로미스 관련 작업이 실패함. \- pending : 프로미스가 아직 resolved 또는 rejected 되지 않은 상태임. \- settled : 프로미스가 fulfilled 또는 rejected 된 상태.
##### Syntax 및 Promise 생성 방법 \- Syntax : new Promise(function(resolve, reject) {...}); \- Promise가 성공적이면 resolve가, 그렇지 않으면 reject가 실행됩니다. \- 함수 실행 중 에러가 발생하면 프로미스는 reject되며, 함수 자체의 반환값은 무시됩니다.

아래는 wait이라는 함수가 호출되면, 함수 내부에서 프로미스로 감싼 setTimeout이 2초 뒤 callback을 비동기적으로 실행시킵니다. 2초 뒤 프로미스가 resolve되고, then()의 callback finish 함수가 호출됩니다.

function wait(ms) {
  return new Promise(function(resolve, reject) {
    window.setTimeout(function() {
      resolve();
    }, 2000);
  });
}

const milliSeconds = 2000;
wait(milliSeconds).then(finish);

function finish() {
  var completion = document.querySelector('.completion');
  completion.innerHTML = 'Complete after ' + milliSeconds + 'ms.';
}

다음은 Promises를 이용해 document의 readystate가 'interactive' 상태일 때 실행이 되는 코드를 작성해 보도록 하겠습니다. DOM의 일부 이미지나 style sheet이 로드가 완료되기 전, 어떠한 작업을 하고 싶을 때 이와같이 작성할 수 있습니다.

아래의 코드를 크롬의 개발자 툴에서 네트워크 제한을 걸어 속도가 느린 인터넷 환경으로 테스트를 해보면 다른 DOM 요소(예, 큰 사이즈의 파일)가 온전히 로드 되기 전 코드가 실행되는 것을 확인할 수 있습니다.

function ready() {
  return new Promise(function(resolve, reject) {
    document.addEventListener('readystatechange', function() {
      if (document.readyState !== 'load') {
        resolve();
      }
    });
  });
}

ready().then(wrapperResolved);

function wrapperResolved() {
  var completion = document.querySelector('.completion');
  completion.innerHTML = 'Resolved!';
}

##### 프로미스를 XHR과 사용 HTTP request를 보내기 위해 XHR을 사용할 수 있는데, 아래의 코드는 프로미스를 적용한 코드입니다.
function get(url) {

  return new Promise(function(resolve, reject) {

    var req = new XMLHttqRequest();
    req.open('GET', url);
    req.onload function() {
      if(req.status === 200 ) {
        resolve(req.response);
      } else {
        reject(req.statusText);
      }
    };

    req.onerror = function() {
      reject(Error('Network Error'));
    };
    req.send();

});
};

프로미스를 Fetch API와 사용

위의 코드와 같이 HTTP 요청을 발송하기 위해, XHR사용은 사전 작업이 많고 다소 복잡하게 느껴집니다. Fetch API를 이용하면 XHR보다 명료하게 코드를 작성할 수 있습니다.

Fetch API 관련 포스팅 링크 XMLHttpRequest 관련 포스팅 링크

.then을 사용함으로써 이전의 데이터를 전달받아 작업할 수 있습니다. .catch를 사용함으로써 네트워크 에러나 fetch request 상 문제가 생겼을 때의 에러를 처리할 수 있습니다.

.then() 또한 Promises를 반환하기 때문에, 또다른 .then()을 연결해 작업을 할 수 있습니다.(thenable)

function get(url) {
  return fetch(url);
}

function getJSON(url) {
  get(url).then(function(response) {
    if (!response.ok) {
      throw Error(response.statusText ? response.statusTex : 'Unknow network error');
    }

    return response.json();
  });
}

window.addEventListener('laod', function() {
  const home = document.querSelector('.section-home');

  getJSON(url)
    .then(function(response) {
      addSearchHeader(response.query);
      console.log(response);
    })
    .catch(function(error) {
      console.log(error);
      addSearchHeader('Unknown');
    });
});

비동기 프로그래밍의 chaning은 작업을 단순화하는데 매우 중요합니다. 각 단계의 chain은 이전 프로미스의 fulfilled value를 받거나, .then의 리턴값을 전달 받습니다.

이러한 방식으로 이전의 비동기 작업에서 데이터를 다음의 작업에 전달할 수 있습니다.