프로미스(Promise) 기초
프로미스(Promise)의 배경
프로미스(Promise)를 한 문장으로 표현하면 "비동기 콜백 지옥의 해결책" 라고 할 수 있습니다.
XMLHttpRequest 객체로 비동기 통신을 하게되면 콜백 함수를 사용해 응답에 대한 처리를 해야합니다.
만약 XMLHttpRequest 객체로 받은 응답을 기초로 다시 비동기 요청을 한다면 어떻게 될까?
실제로 복잡한 서비스를 구현하는 경우 2~3단계 이상 비동기 요청을 하게 되고, 각각의 돌아오는 응답을 처리하는 콜백 함수를 붙이게 됩니다.
개발된 코드는 초기 개발 후 이런 저런 변화와 업데이트를 거치게 되는데, 이렇게 2~3단계로 비동기 요청이 중첩되면, 비동기 호출의 콜백 함수의 어느 부분을 수정 해야하는지 판단하는게 쉽지 않아집니다.
이런 중첩된 비동기 호출과 콜백 함수의 혼돈을 해결하기위해 나온 기술, 또는 기법이 프로미스입니다.
프로미스는 중첩되는 비동기 호출과 응답 처리를 체인 형태로 하나의 긴 연쇄 작업으로 펼쳐줍니다.
개발하는 입장에서는 비동기 처리의 순서가 명확해지기 때문에 유지보수가 조금 더 쉬워집니다.
!주의할 점
프로미스는 인터넷 익스플로러에서는 지원되지 않습니다.
인터넷 익스플로러 호환성이 필요한 경우 프로미스 대신 AJAX 를 위한 XMLHttpRequest 객체를 사용해야 합니다.
프로미스 객체의 상태와 메서드
프로미스는 ES6에서 도입된 객체로 프로미스 인스턴스는 진행중인 비동기 작업과 그에 따르는 결과 처리 방법, 그리고 상태 정보를 가지고 있습니다.
프로미스 객체는 3개의 상태를 가지며, 상태에 따르는 2개의 메서드를 가집니다.
프로미스의 상태와 메서드에 대해서 간단하게 정리해서 알아보겠습니다.
상태 |
설명 |
대기중(Pending) |
프로미스 객체의 기본 상태, 비동기 처리 결과가 나오지 않은 상태. |
이행 완료됨(Fullfilled) |
비동기 처리가 완료되어 결과를 얻은 상태. resolve() 함수를 호출함. |
거부됨(Rejected) |
비동기 처리는 완료되었지만, 실패한 상태. reject() 함수를 호출함. |
then() 메서드 |
비동기 처리가 완료되었을 때 실행되는 메서드. 메서드 파라메터로 콜백 함수를 등록하며, 콜백함수 파라메터로 결과 데이터, 또는 메시지를 받아 최종 데이터 처리 작업을 할 수 있습니다. |
catch() 메서드 |
프로미스가 거부되었을 때 호출되는 메서드, 메서드 파라메터로 콜백 함수를 등록하며, 거부시에 콜백 함수가 실행됩니다. 콜백 함수 파라메터로 에러 메시지를 받을 수 있습니다. |
대기중 상태는 비동기 요청이 처리중인 상태로 이행 완료, 또는 거부되기 전에 결과(응답)를 기다리고 있는 상태입니다.
이행 완료 상태가 되면 프로미스 인스턴스는 then() 메서드의 파라메터로 작성한 콜백 함수를 호출합니다.
거부 상태가 되면 catch() 메서드의 파라메터로 작성한 콜백 함수를 호출합니다.
프로미스 기본 사용법
프로미스 기본 구문은 다음과 같습니다.
let myPromise = new Promise((resolve, reject)=>{ //비동기 원격 요청을 대신해 가상으로 타이머로 지연해서 이행 함수를 호출함. setTimeout(()=>{ let result = 'promise fulfilled'; resolve(result);//비동기 수신 결과를 인자로 넣어 콜백을 },1000);}); myPromise.then((successMessage)=>{ //resolve 에서 받은 파라메터 정보를 콘솔에 출력 console.log(successMessage);}).catch((failMessage)=>{ //reject() 에러 메시지 출력 console.log(failMessage);});;
먼저 프로미스 인스턴스를 생성하는 것부터 확인합니다.
프로미스 객체를 생성할 때는 인자로 파라메터 2개를 가지는 비동기 처리 함수를 작성합니다.
파라메터 2개중 첫번째는 이행이 완료되었을 때 호출하는 이행 완료 함수명이고, 2번째 파라메터는 실패했을 때 호출하는 거부 함수명입니다.
편의상, 또는 관습적으로 이행 완료, 실패 함수명을 "resolve", "reject" 로 사용합니다.
파라메터의 함수명은 다른 것으로 바꿀 수 있으며, 비동기 함수 안에서 이행이 완료되었을 때, 또는 실패했을 때 호출하는 함수명과 일치하면 됩니다.
예를 들어 아래와 같이 작성할 수 있습니다.
let myPromise = new Promise((success, fail)=>{ //비동기 원격 처리 success(result);});
setTimeout() 은 가상으로 AJAX 원격 호출에 대한 응답을 흉내내기 위해서 사용한 것입니다
실제로는 비동기로 서버에서 원격 요청을 보내 응답을 받는 코드가 들어가게 됩니다.
생성자 파라메터에 정한 함수명을 호출하면, 프로미스 인스턴스에서 메서드 체인으로 then() 과 catch() 중 하나를 호출합니다.
이행이 완료되어 resolve() 함수를 호출하면 then() 메서드의 파라메터에 정의한 콜백 함수가 실행됩니다.
이행이 거부되어 reject() 함수를 호출하면 catch() 메서드의 파라메터에 정의한 콜백 함수가 실행됩니다.
then() 과 catch()의 콜백 함수는 파라메터를 통해 데이터, 또는 메시지를 전달할 수 있습니다.
프로미스를 이용해 비동기 통신 1개를 처리하는 것은 XMLHttpRequest 를 이용해 AJAX 통신을 하는 방법과 별다른 차이점이나 장점이 없습니다.
다중 프로미스 처리
AJAX 원격 호출을 중첩해서 호출하는 경우 프로미스는 XMLHttpRequest 를 사용하는 방법과는 다른 방법으로 접근합니다.
예를 먼저 들어보겠습니다.
//프로미스 인스턴스 리턴function asyncWork(value){ return new Promise((resolve, reject)=>{ setTimeout(()=>{ value -= 20; if(value > 50){ resolve(value); }else{ reject(value); } },1000); });}
//프로미스 요청/응답 체인asyncWork(100).then((value)=>{ console.log(value);return asyncWork(value);}).then((value)=>{ console.log(value);return asyncWork(value);}).then((value)=>{ console.log(value);return asyncWork(value);}).catch(err=>{ console.log('catch:'+err); });
먼저 프로미스 인스턴스를 반환하는 함수(asyncWork)를 하나 만듭니다.
여러 개의 원격 요청을 반복 생성하기 위해 작성한 함수이고, 실제 작업에서는 여러개의 비동기 요청 함수를 만들 수 있습니다.
asyncWork() 함수는 프로미스 인스턴스를 반환하므로 함수를 호출하면서 메서드 체인으로 then()을 호출할 수 있습니다.
예제를 보면 then()을 연달아 체인 방식으로 여러개를 호출합니다.
다중 프로미스의 핵심은 이렇게 체인으로 호출하는 then() 메서드의 콜백 함수의 반환(return)값으로 프로미스 인스턴스를 생성하는 함수를 다시 호출한다는 것입니다.
반환된 프로미스 인스턴스는 다음번 then()의 프로미스 인스턴스가 됩니다.
then() 메서드는 콜백함수에서 반환하는 asyncWork() 프로미스 인스턴스의 비동기 처리가 완료되기 전까지 다음번 then()을 호출하지 않고 대기합니다.
따라서 모든 비동기 처리 작업은 then(콜백함수) 순서대로 하나씩 실행되고, 콜백함수의 파라메터를 통해 결과값을 순차적으로 전달합니다.
순서대로 then() 메서드르 붙여서 나열하기 때문에 중첩 레벨을 내려갈일이 없습니다.
마지막으로 프로미스 인스턴스가 상태가 거절 상태가 되면 다음번 then()으로 가지 않고 catch()로 바로 빠지면서 거절 메시지를 출력하고 종료합니다.
예제에서는 then()의 콜백함수 안에서 값을 20씩 차감하면서 다음번 then()으로 값을 전달합니다.
값이 50보다 작으면 거절 상태로 변경한 후(reject(value)) catch()를 호출하면서 비동기 처리 체인을 종료합니다.
비동기 처리 작업(여기서는 타이머로 가상으로 처리)을 중복 작성할 필요가 없기 때문에 비동기 처리 구조의 효율이 좋아질 뿐만 아니라, 코드량도 줄어듭니다.