Promise
function callbackDelay(sec, callback) {
setTimeout(() => {
callback('콜백 Delay>>' + new Date().toString());
}, sec * 1000);
}
callbackDelay(1, (result) => {
console.log(1, result);
callbackDelay(1, (result) => {
console.log(2, result);
callbackDelay(1, (result) => {
console.log(3, result);
});
});
});
/*result
1 콜백 Delay>>Sun Jun 07 2020 18:43:56 GMT+0900 (GMT+09:00)
promise.js:26
2 콜백 Delay>>Sun Jun 07 2020 18:43:57 GMT+0900 (GMT+09:00)
promise.js:28
3 콜백 Delay>>Sun Jun 07 2020 18:43:58 GMT+0900 (GMT+09:00)
*/
자바스크립트에서 비동기 동작을 할때 우리는 항상 해당 함수에 콜백 함수를 넣어줬다. 만약 타이머로 1초마다 해당 시간을 출력하는 함수를 예시로 들면..
대표적인 비동기 함수인 setTimeout을 호출할 때 이렇게 매 순간 콜백 함수를 전달해야 1..2..3..형태로 출력되는 것을 볼 수 있다. 하지만 이 코드의 불편함은 콜백이 콜백을 전달함으로써 **콜백지옥(callback hell)**이 펼쳐진다는것이다. 이를 개선하기 위해 Promise
객체를 사용하면 더욱 간결하게 비동기 로직에 대응할 수 있다.
사용법
Promise에는 3가지 상태가 있다. (대기 - 이행 - 실패)
-
대기는 프로미스를 생성자로 호출한 순간이다. 프로미스는 콜백 함수를 인수로 받고, 성공(resolve) - 실패(reject) 상태를 대응할 수 있다.
new Promise(function (resolve, reject) { //code.. });
-
resolve
를 호출할 시 이행 상태가 된다.new Promise(function (resolve, reject) { resolve(); });
-
이행(완료) 상태가 되면
then()
메서드로 콜백함수를 전달할 수 있다.new Promise(function (resolve, reject) { const result = 'success!'; resolve(); }).then(function (res) { console.log(res); }); //success!
-
실패 상태가 되면
catch()
메서드로 받을 수 있다.new Promise(function (resolve, reject) { const fail = 'fail!'; reject(new Error(fail)); }) .then() .catch(function (res) { console.log(res); }); //fail!
또한 여러개의 Promise들의 인스턴스를 순차적으로 연결시킬 수 있다.
function getData() {
return new Promise(function (resolve, reject) {
resolve('success!!'); //then method chain
});
}
getData()
.then(function (data) {
console.log(data + 'first');
return data;
}) //success!!first
.then(function (data) {
console.log(data + 'second');
return data;
})
.then(function (data) {
console.log(data + 'third');
});
이제 기존 단순히 콜백을 전달하는 방식으로 작성한 타이머 코드를 프로미스 형태로 개선해보자.
function promiseDelay(sec) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('프로미스 Delay>>' + new Date().toString());
}, sec * 1000);
});
}
promiseDelay(1)
.then((res) => {
console.log(1, res);
return promiseDelay(1); //프로미스를 다시 호출하지 않으면 undefined
})
.then((res) => {
console.log(2, res);
});
return 활용법
프로미스 내부에서는 return 을 사용하지 않고, resolve()만 시행시킨다면 후행의 코드들도 모두 실행한다.
따라서 즉시 then(이행 상태)으로 넘어가려면 return
을 해주면 된다. (함수 return 방식과 동일하다.)
return new Promise((resolve, reject) => {
attach.readFile((data) => {
resolve(data);
console.log('success'); // 출력
});
});
catch
문 내부에서 return을 통해 에러를 효율적으로 처리할 수있다.
에러가 발생하면 catch메서드에서 처리하게되는데 , 다시 then으로 넘겨 에러 이후의 처리들도 할 수 있다.
const getUserInfo = () =>
new Promise((resolve, reject) => {
setTimeout(() => resolve({ id: 'abc', pw: 'qwerty' }), 1000);
});
const executeHandler = (userInfo) =>
new Promise((resolve, reject) => {
console.log(JSON.stringify(userInfo));
setTimeout(() => reject(new Error('fail process')), 1000);
});
getUserInfo()
.then((userInfo) => executeHandler(userInfo))
.catch((error) => {
return `'${error}' handlling success!!`;
}) //promise Chain, 오류가 발생하더라도 다시 값을 처리해 .then(result)로 넘길 수 있다.
.then((result) => console.log(result));
Async-await
Promise를 더욱 간결하게 처리할 수 있는 문법이다. 함수 앞에 async
키워드를 선언하면 된다.
await
예약어는 해당 함수가 꼭 프로미스 객체를 반환해야 한다.
function getlUserApi(){
//promise 반환 (비동기 함수)
return new Promise((resolve, reject) => { //..code });
};
async function showUser(){
const userInfo = await getUserApi();
return userInfo;
}
위 코드는 아래와 같이 Promise를 반환시킨다. 따라서 프로미스와 마찬가지로 then
을 통해 콜백 함수를 처리한다.
async function showUser() {
const userInfo = getUssrApi();
return Promise.resolve(userInfo);
}
만약 사용자의 정보를 받아오는 함수가 있다면 아래와 같이 사용할 수 있다.
//AJAX 대신 setTimeout으로 대체;
function getUserApi() {
return new Promise((resolve, reject) => {
//Promise 반환
setTimeout(() => {
resolve({ name: 'jiny', age: 27 });
}, 3000);
});
}
//async 함수 내부에 await 선언
async function showUser() {
const userInfo = await getUserApi();
return userInfo;
}
//이행 처리
showUser().then((data) => {
console.log(JSON.stringify(data));
});
에러처리
async 함수내에서 오류가 발생하면 try-catch
문으로 에러를 핸들링해줄 수 있다.
물론 catch()
로 프로미스에서 해결하지 못한 에러들도 추가적으로 처리가 가능하다.
async function showUser() {
try {
const result = await getUserApi();
return userInfo;
} catch (error) {
console.error(error);
}
}
Top-level await
chrome 89+, Node.js 14+ 버전부터 async 함수 예외에서도 await을 사용할 수 있다고한다.
최신 브라우저에서는 async로 굳이 감싸지 않아도 될듯하다.. (실제 테스트해보았더니 잘 실행된다!)
await getUserApi()
.then(function(res){ console.log(JSON.stringify(res) });
참고자료
- Node.js 교과서 - 프로미스 (조현영 저)
- 자바스크립트 코딩의 기술 - async/await (조 모건 저)
- async와 await - Frontend.INFO
- toplevel await - v8.dev