September 30th 2018
"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가 출력이됨.
아래는 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!';
}
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();
});
};
위의 코드와 같이 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의 리턴값을 전달 받습니다.
이러한 방식으로 이전의 비동기 작업에서 데이터를 다음의 작업에 전달할 수 있습니다.