PROMISE

앞선 글의 콜백 지옥 을 해결하기 위한 방법 중 하나로 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의 states

Promise는 사전적 의미로 "약속"이다. js에서는 "지금은 없는데 이상없으면 이따가 주고 없으면 알려줄게"라는 약속으로 볼 수 있다. 따라서 promise는 다음과 같은 상태(state)를 가진다.

  1. pending(대기) : 비동기 처리 로직이 아직 완료되지 않은 상태. (약속을 수행 중인 상태)

  2. fulfilled(이행) : 비동기 처리가 완료되어 promise가 결과 값을 반환해준 상태. (약속이 지켜진 상태)

  3. rejected(실패) : 비동기 처리가 실패하거나 오류가 발생한 상태(약속이 못 지켜진 상태)

  4. settled : 비동기 처리가 실패이든 성공이든 결론이 난 상태이다.

각 상태를 차례대로 살펴보자.

Pending

new Promise();

다음과 같이 new Promise() 메서드를 호출하면 pending 상태가 된다.

new Promise(function(resolve, reject){
    //...
});

메서드를 호출할 때 콜백 함수의 인자로 resolve, reject 에 접근 할 수 있다.

promsie가 생성된 직후부터 resolve나 reject가 호출되기 전까지의 상태이다.

Fulfilled

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

Rejected

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 Chaining

여러개의 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() 으로 연결하여 처리할 수 있다.

Promise.all

여러개의 비동기 작업들이 존재하고 그 작업들이 모두 완료되었을 때 다음 작업을 진행하고 싶은 경우에 활용하면된다.

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"
//=> "해결"
//=> "모두 완료"

참조페이지

Last updated