앞선 글의 콜백 지옥 을 해결하기 위한 방법 중 하나로 Promise 패턴이 제안되었다.
jQuery에서는 완전하진 않지만 Promise패턴이 Deferred 로 사용되고 있다.
Promise 패턴을 사용하면 1. 비동기 작업들을 순차적으로 진행하고나, 병렬로 진행하는 등의 제어가 수월해지고 2. 코드의 가독성이 좋아진다. 또한 내부적으로 예외처리에 대한 구조가 탄탄해, 오류 발생 시 3. 오류 처리에 대해 보다 직관적으로 관리해줄 수 있는 장점이 있다.
Promise는 비동기 처리가 성공(fulfilled)했는지, 실패(rejected)했는지 등의 상태 정보와 처리 종료후 실행될 콜백함수(then, catch)를 담고 있는 객체이다.
Promise는 주로 서버에서 받아온 데이터를 화면에 표시할 때 사용하며, 일반적으로 웹 애플리케이션을 구현할 때 서버에서 데이터를 요청하고 받아오기 위해 다음과 같이 사용한다.
var _promise = function(param){
return new Promise(function (){
// setTimeout함수로 비동기
window.setTimeout(function(){
if(param){
resolve("해결완료");
}else{
reject(Error("실패"));
}
},30000);
});
};
//promise 실행
_promise(true).then(function(text){
console.log(text);
},function(error){
console.error(error);
});
//=> "해결 완료"
function getData(callback) {
// new Promise()
return new Promise(function (resolve, reject) {
$.get('url 주소/products/1', function (response) {
resolve(response); // 데이터를 받으면 resolve()
});
});
}
// getData()의 실행이 끝나면 호출되는 then()
getData().then(function (data) {
// resolve()의 결과 값이 전달된다.
console.log(data); // $.get()의 reponse 값이 tableData에 전달됨
});
위의 코드는 Promise 선언과 실행 두 부분으로 나눌 수 있다.
Promise는 사전적 의미로 "약속"이다. js에서는 "지금은 없는데 이상없으면 이따가 주고 없으면 알려줄게"라는 약속으로 볼 수 있다. 따라서 promise는 다음과 같은 상태(state)를 가진다.
- pending(대기) : 비동기 처리 로직이 아직 완료되지 않은 상태. (약속을 수행 중인 상태)
- fulfilled(이행) : 비동기 처리가 완료되어 promise가 결과 값을 반환해준 상태. (약속이 지켜진 상태)
- rejected(실패) : 비동기 처리가 실패하거나 오류가 발생한 상태(약속이 못 지켜진 상태)
- settled : 비동기 처리가 실패이든 성공이든 결론이 난 상태이다.
각 상태를 차례대로 살펴보자.
new Promise();
다음과 같이 new Promise()
메서드를 호출하면 pending 상태가 된다.
new Promise(function(resolve, reject){
//...
});
메서드를 호출할 때 콜백 함수의 인자로 resolve, reject 에 접근 할 수 있다.
promsie가 생성된 직후부터 resolve나 reject가 호출되기 전까지의 상태이다.
new Promise(function(resolve, reject){
resolve();
});
콜백 함수의 인자인 resolve를 다음과 같이 실행하면 fulfilled(이행) 상태가 된다. fulfilled상태가 되면 아래와 같이 then()
을 이용해 처리 결과 값을 받을 수 있다.
function getData(){
return new Promise(function(resolve, reject){
var data = 100;
resolve(data);
});
}
getData().then(function(resolvedData){
console.log(resolvedData);
});
//=> 100
new Promise(function(resolve,reject){
reject();
});
콜백 함수 인자인 reject로 reject()
메서드를 실행하면 rejected(실패) 상태가된다. rejected 상태가 되면 실패한 이유(실패 처리의 결과 값)를 아래와 같이 catch()
로 받을 수 있다.
function getData(){
return new Promise(function(resolve,reject){
reject(new Error("Request is failed"));
});
}
getData.then().catch(function(err){
console.log(err);
});
//=> "Request is failed"
오류가 발생했을 때 catch()
말고 then()
만을 사용하여 처리할 수도 있다.
getData.then(function(){
// 성공...
},function(err){
console.log(err);
});
두 가지 방법을 사용해서 오류를 처리할 수 있지만 가급적이면 catch 를 사용하는 것이 좋다. 왜냐하면 then()의 두번째 인자로는 다음 예시와 같이 오류를 감지 못하는 경우가 있기 때문이다.
function getData(){
return new Promise(function(resolve,reject){
resolve('hi');
});
}
getData.then(function(result){
console.log(result);
throw new Error("Error in then() 인지 못하는 오류");
},function(err){
console.log('then error : ',error);
});
더 많은 예외 처리 상황을 위해서는 catch()를 사용하는 것이 좋다.
function getData(){
return new Promise(function(resolve, reject){
$.get(url,function(response){
if(response){
resolve(response);
}
reject(new Error("Request is failed"));
});
});
}
getData().then(function(data){
console.log(data);
}).catch(function(err){
console.log(err);
})
위의 코드는 서버에서 응답을 제대로 받아오면 resolve() 메서드 호출, 응답이 없으면 reject() 메서드를 호출하는 예제이다. 호출된 메서드에 따라 then()이나 catch()로 분기하여 결과값을 출력한다.
여러개의 Promise 여러개를 연결하여 사용할 수 있다.
function getData(){
return new Promise({
//...
});
}
getData().then(function(data){
//...
}).then(function(){
//...
}).then(function(){
//...
});
다음과 같이 then()
메서드를 호출하여 여러개를 연결 할 수 있다.
new Promise(function(resolve, reject){
setTimeout(function(){
resolve(1);
},2000);
})
.then(function(result){
console.log(result); //=>1
return result+10;
})
.then(function(result){
console.log(result); //=>11
return result+10;
})
.then(function(result){
console.log(result); //=>21
});
실무에서 있을 법한 예시는 다음과 같다.
예를 들어, 사용자 정보를 받아와 파싱, 인증 등의 작업을 거치는 코드를 살펴볼 것이다.
var userInfo = {
id: 'test@xx.xxx',
pw: '11111'
};
function parseValue(){
return new Promise({
//...
});
}
function auth(){
return new Promise({
//...
});
}
function display(){
return new Promise({
//...
});
}
getData(userInfo)
.then(parseValue)
.then(auth)
.then(display);
다음과 같이 여러개의 프로미스를 .then()
으로 연결하여 처리할 수 있다.
여러개의 비동기 작업들이 존재하고 그 작업들이 모두 완료되었을 때 다음 작업을 진행하고 싶은 경우에 활용하면된다.
var _promise = new Promise(function(resolve, reject){
window.setTimeout(function(){
console.log("해결");
resolve("first");
},Math.random()*20000+1000);
});
var _promise2 = new Promise(function(resolve, reject){
window.setTimeout(function(){
console.log("해결2");
resolve("second");
},Math.random()*10000+1000);
});
Promise.all([_promise, _promise2]).then(function(values){
console.log("모두 완료",values);
});
//=> "해결2"
//=> "해결"
//=> "모두 완료"