-
JavaScript 비동기 처리를 위한 Promise개발 2020. 5. 19. 22:30
비동기 처리를 작성하다 보면 콜백 함수가 중첩되어 알아보기 어려운 코드가 되어버리는 상황이 종종 발생합니다.
이때 Promise를 사용하면 복잡하게 중첩된 코드를 좀 더 알아보기 쉽게 작성할 수 있습니다.
Promise의 기본
Promise는 비동기 처리를 실행하고 그 처리가 끝난 후에 다음 처리를 실행하기 위해 사용합니다.
Promise를 사용하려면 다음과 같이 Promise 객체를 생성해야 합니다.
let promise = new Promise(function(resolve, reject){ ... });
Promise 에는 실행하고자 하는 처리를 작성한 함수를 인수로 넘깁니다.
이 함수는 resolve와 reject라는 인수를 받고 이 인수는 콜백 함수 입니다.
- resolve
함수 안의 처리가 성공적으로 끝났을 때 호출해야 하는 콜백 함수 입니다. resolve 함수에는 어떠한 값도 인수로 넘길 수 있습니다.
이 값은 다음 처리를 실행하는 함수에 전달됩니다. - reject
함수 안의 처리가 실패했을 때 호출해야 하는 콜백 함수 입니다. reject 함수 또한 어떠한 값도 인수로 넘길 수 있습니다.
대부분의 경우 오류 메시지 문자열을 인수로 사용합니다.
다음은 resolve와 reject를 사용한 예제입니다.
let promise = new Promise(function(resolve, reject){ setTimeout(function(){ console.log("A"); resolve(); }, 1000); }); promise.then(function(){ console.log("B"); });
실행 결과는 다음과 같습니다.
과정을 살펴보면 이 코드를 실행하면 1초 후에 "A"가 출력되고, 그 다음에 "B"가 출력됩니다.
이 코드에서 Promise에 인수로 넘긴 함수는 비동기 처리를 수행하는 함수이며, 1초 후에 "A"를 출력하고, 그 다음에는 함수 resolve()를 호출해서 Promise 안의 처리를 종료 시킵니다. resolve()함수가 실행되면 then 메서드에 등록한 함수가 호출되는 것입니다.
Promise를 종료시키는 resolve 함수와 then메서드
resolve 함수는 Promise를 종료시킵니다. resolve 함수에 인수로 넘긴 값은 then 메서드에 인수로 넘긴 함수에 전달되어 다음 처리를 위해 사용됩니다. then메서드의 사용법은 다음과 같습니다.
promise.then(successFunc);
successFunc 함수는 성공 콜백 함수라고 하며 promise 안의 처리가 정상적으로 끝났을 때 호출되는 함수입니다.
successFunc 함수는 인수로 response를 받습니다. response는 promise 안에서 resolve 함수를 실행할 때 넘긴 인수입니다.
코드로 직접 보면 다음과 같습니다.
let promise = new Promise(function(resolve, reject){ setTimeout(function(){ let name = "foo"; resolve(name); }, 1000); }); promise.then(function(name){ console.log(`Hi! ${name}`); // Hi! foo });
Promise를 실패로 처리하는 reject 함수와 catch 메서드
reject 함수는 실패 상태가 되어 Promise를 종료시킵니다. resolve 함수와 마찬가지로 reject 함수에도 값을 넘길 수 있습니다.
reject함수가 실행되면 then 메서드에 넘긴 함수는 실행되지 않고 catch 메서드에 넘긴 함수가 실행됩니다.
catch 메서드의 사용법은 다음과 같습니다.
promise.catch(failedFunc);
failedFunc 함수는 실패 콜백 함수라고 하며 promise 안의 처리가 실패로 종료됬을 때 호출되는 함수입니다.
failedFunc 함수는 인수로 error를 받으며 error는 promise안에서 reject 함수를 실행 했을 때 넘긴 인수입니다.
reject를 사용한 코드를 예제로 보겠습니다.
let promise = new Promise(function(resolve, reject){ setTimeout(function(){ let n = parseInt(prompt("10 미만의 숫자를 입력하세요.")); if(n <= 10){ resolve(n); }else { reject(`${n}은 10 이상 입니다.`); } }, 1000); }); promise .then(function(num){ console.log(`${num}은 정답입니다.`); }) .catch(function(error){ console.log(error); });
위의 코드를 실행하면 1초 후에 "10미만의 숫자를 입력하세요" 라는 promp창이 뜹니다. 입력한 숫자가 10 미만이면 then에 넘긴 함수가 실행되어 입력한 값이 정답이라고 콘솔에 출력할 것이고, 10 이상이면 catch에 넘긴 함수가 실행되어 입력한 값은 10 이상입니다. 라고 error메시지를 출력할 것입니다.
then의 두 번째 인수로 실패 콜백 함수 넣기
then 메서드에는 두 번째 인수로 실패 콜백 함수를 지정할 수 있습니다.
그렇다면 then 메서드에 처리할 내용과 catch 메서드에서 처리할 내용을 then 메서드 하나로 작성할 수 있게 됩니다.
두 번째 인수를 지정한 then의 사용 법은 다음과 같습니다.
promise.then(successFunc, failedFunc);
promise 안의 처리가 성공하면 successFunc 함수가 실행되고, 실패하면 failedFunc 함수가 실행됩니다.
then의 두 번째 인수를 사용하면 catch 메서드를 설명할 때 작성했던 예제 코드를 다음과 같이 수정할 수 있습니다.
let promise = new Promise(function(resolve, reject){ setTimeout(function(){ let n = parseInt(prompt("10 미만의 숫자를 입력하세요.")); if(n <= 10){ resolve(n); }else { reject(`${n}은 10 이상 입니다.`); } }, 1000); }); promise.then( // 처리가 성공적으로 종료되었을 때 호출되는 콜백 함수 function(num){ console.log(`${num}은 정답입니다.`); }, // 처리가 실패로 종료되었을 때 호출되는 콜백 함수 function(error){ console.log(error); } );
훨씬 더 간결하게 코드를 작성할 수 있겠지만 가급적 catch로 에러를 처리하는게 더 효율적입니다.
그 이유는 다음과 같습니다.
let promise = new Promise(function(resolve, reject){ setTimeout(function(){ let n = parseInt(prompt("10 미만의 숫자를 입력하세요.")); if(n <= 10){ resolve(n); }else { reject(`${n}은 10 이상 입니다.`); } }, 1000); }); promise.then( // 처리가 성공적으로 종료되었을 때 호출되는 콜백 함수 function(num){ console.log(`${num}은 정답입니다.`); // 오류 객체 생성 throw new Error("Error in then()"); }, // 처리가 실패로 종료되었을 때 호출되는 콜백 함수 function(error){ console.log(error); } );
처리가 성공적으로 종료되었을 때 호출되는 첫 번째 then 함수에서 throw new Error()를 통해 오류 객체를 생성하였습니다.
결과로는 다음과 같은 에러가 나옵니다.
resolve() 메서드를 호출하여 정상적으로 로직을 처리했지만, then()의 첫 번째 콜백 함수 내부에서 오류가 나는 경우 오류를 제대로 잡아내지 못합니다.
하지만 똑같은 오류를 catch() 로 처리하면 다른 결과가 나옵니다.
let promise = new Promise(function(resolve, reject){ setTimeout(function(){ let n = parseInt(prompt("10 미만의 숫자를 입력하세요.")); if(n <= 10){ resolve(n); }else { reject(`${n}은 10 이상 입니다.`); } }, 1000); }); promise .then(function(num){ console.log(`${num}은 정답입니다.`); // 오류 객체 생성 throw new Error("Error in then()"); }) .catch(function(error){ console.log(error); });
출력 결과
발생한 에러를 성공적으로 출력하는 것을 확인할 수 있습니다.
따라서 더 많은 예외 처리 상황을 위해 프로미스의 끝에 가급적 catch()를 붙이는 것이 더 좋습니다.
여러개의 Promise 연결하기 (Promise Chaning)
Promise로 비동기 처리를 여러 개 연결해서 순차적으로 실행하려면 then 메서드 안에서 실행하는 성공 콜백 함수가 Promise 객체를 리턴하도록 합니다. 그러면 then 메서드 체인으로 Promise 작업을 연결할 수 있습니다.
let promise = new Promise(function(resolve, reject){ setTimeout(function(){ let n = parseInt(prompt("10 미만의 숫자를 입력하세요.")); if(n <= 10){ resolve(n); }else { reject(`${n}은 10 이상 입니다.`); } }, 1000); }); promise .then(function(num){ console.log(`${num}은 정답입니다.`); return num + 1; }) .then(function(value){ console.log(value); });
resolve 함수에 인자로 들어간 값이 첫 번째 then() 메서드를 거치면서 성공적으로 콘솔에 출력한 후 이 값에 + 1 한 값이 두 번째 then() 메서드의 콜백함수의 인자로 받아서 콘솔에 출력합니다.
이 처럼 promise를 리턴하는 then의 특성으로 계속해서 체이닝 패턴이 사용 가능하고, 값을 조작할 수 있습니다.
Promise를 사용하여 콜백 지옥 해결하기
Promise 를 사용하기 전에 비동기 처리는 콜백함수를 설정하는 방식으로 이루어졌습니다.
비동기가 완료되는 시점에 실행이 되는 콜백 함수로 완료를 인지하고 그 다음 처리를 하는 것입니다. 그렇게 하다보니 비동기 처리를 연속적으로 해야하는 경우 콜백 지옥에 빠지게 되는 것입니다.
a(function(valueA){ b(function(valueB){ c(function(valueC){ d(function(valueD){ .... }); }); }); });
위의 코드를 Promise를 이용하여 콜백 지옥을 완전히 탈출할 수 있는 것은 아니지만 콜백 함수를 바로 넘겨받지 않고 객체에 이어서 사용할 수 있게 되면서 훨씬 가독성이 좋아졌습니다.
promise.then(function(a){ }).then(function(b){ }).then(function(c){ }).then(function(d){ });
보시는 것과 같이 훨씬 더 가독성이 좋아진 것을 알 수 있습니다.
Promise는 비동기 처리에 있어서 객체의 개념을 도입했다는 점이 가장 큰 특징입니다.
이 Promise 만으로도 콜백 지옥을 해결할 수 있겠지만 자바스크립트 스펙 ES2017에 나온 최신 기능인 Async/Await 문법은 프로미스를 사용한 코드를 한번 더 깔끔하게 해줄 수 있습니다.
바로가기 : 자바스크립트 Async 와 Await
'개발' 카테고리의 다른 글
web security ( XSS, CSRF, CORS와 OPTIONS 메서드 ) (0) 2020.05.20 http 개체와 서버구축 (bare code) (2) 2020.05.20 JavaScript 비동기 처리와 콜백 함수 (0) 2020.05.19 HTTP 통신 (0) 2020.05.19 Object.create()와 constructor (2) 2020.05.11 - resolve