
💌 이 글은 드림코딩의 비동기 3부작 | 콜백 - 프로미스 - async & await 에서 async & await에 대해 다룹니다
async & await앞서 콜백 지옥을 해결하기 위해 프로미스를 쓴다고 공부했다. 그렇다면 프로미스는 아무런 문제가 없는 방법일까?
💬 예제 1
function delay(ms) {
return new Promise (resolve => setTimeout(resolve, ms));
}
function getApple() {
return delay(1000)
.then(() => `🍎`);
}
function getBanana() {
return delay(1000)
.then(() => `🍌`);
}
function pickFruits() {
return getApple().then(apple => {
return getBanana().then(banana => `${apple} + ${banana}`);
}); // 🔥 콜백 지옥의 향기...
}
pickFruits().then(console.log); // 🍎 + 🍌
위의 예제코드에서 알 수 있듯 프로미스도 여러번 중첩해서 사용하면 결국 콜백 지옥과 똑같은 문제가 발생한다. 이 문제는 async와 await을 통해 해결할 수 있다.
async & await를 함께 사용하여 읽고 쓰기 쉬운 비동기 코드를 작성할 수 있다.async & await를 사용하면 promise.then이 거의 필요 없다.async & await는 프로미스를 기반으로 한다.asyncasync를 통해 프로미스를 좀 더 편하게 사용할 수 있다. 이것은 새로운 것이 아니라, 기존의 프로미스를 베이스로 API를 추가하는 것으로, Syntactic Sugar의 일종이다.
✔ Class 또한 프로토타입을 베이스로 하는 것으로, Syntactic Sugar의 일종이다.
프로미스를 쓰지 않아도 함수안의 코드블럭이 자동으로 프로미스로 변환된다.
async가 앞에 붙은 함수는 항상 프로미스를 반환한다. 프로미스가 아닌 값도 resolved promise로 감싸, resolved promise가 반환되게 한다.
async 문법함수 앞에
async를 붙이는 아주 간단한 방법이다.async function f() { return 1; } const user = f(); // async가 붙은 함수는 항상 프로미스를 반환한다. console.log(user); // Promise {<fulfilled>: 1} // 호출 방법 // 1. 변수에 할당해서 호출 user.then(console.log); // 1 // 2. 함수로 바로 호출 f().then(console.log); // 1
await동기적인 코드를 쓰는 것처럼 만들어 promise.then보다 가독성 좋게 프로미스의 result 값을 얻을 수 있게 해준다.
async가 붙은 함수 안에서만 사용 가능하다.
프로미스 앞에 await을 붙이면 자바스크립트는 프로미스가 처리(settled)될 때까지 기다린다.
💬 예제 2
async function f() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("완료!"), 1000)
});
let result = await promise; // 프로미스가 처리될 때까지 기다림(실행 중단)
console.log(result); // 1초뒤 완료!가 콘솔에 찍힘
}
f();
💬 예제 3
function delay(ms){
return new Promise(resolve => setTimeout(resolve, ms))
};
async function getApple(){
await delay(1000); // delay가 끝날 때까지 기다림
return '🍎'; // delay 처리 후 '🍎'을 리턴하는 프로미스가 async에 의해 만들어짐
}
getApple().then(console.log); // 🍎
// 위의 async를 promise chaining으로 표현하면?
function getApple(){
return delay(1000).then(()=>'🍎');
}
💬 예제 4 | 예제 1의 promise.then을 async & await으로 바꿔보자
// 예제 1 일부
function pickFruits() {
return getApple().then(apple => {
return getBanana().then(banana => `${apple} + ${banana}`);
}); // 콜백 지옥의 향기...🔥
}
pickFruits().then(console.log); // 🍎 + 🍌
// async & await으로 변환
async function pickFruits() {
const apple = await getApple();
const banana = await getBanana();
return `${apple} + ${banana}`;
}
pickFruits().then(console.log); // 🍎 + 🍌
💬 예제 4 에서, getApple()과 getBanana()는 연관성이 없으므로 서로 기다려주지 않고 동시에 병렬적으로 실행되어도 된다. 이를 위해 프로미스를 선언하여 즉시 실행되게 만든다.
async function pickFruits() {
const applePromise = getApple(); // 프로미스는 선언 즉시 바로 실행됨
const bananaPromise = getBanana();
// 동기화
const apple = await applePromise;
const banana = await bananaPromise;
return `${apple} + ${banana}`;
}
pickFruits().then(console.log); // 🍎 + 🍌
그러나 이렇게 더럽게 작성하는 것보다, Promise.all 메소드를 사용하여 깔끔하게 작성하는 것이 좋다.
Promise.all사용하기
- 배열 내 모든 값의 이행(또는 첫 번째 거부)을 기다립니다. MDN
Promise.all([프로미스1, 프로미스2, 프로미스3]) .then(values => { console.log(values); });function pickAllFruits() { // 프로미스 처리 결과가 담긴 배열을 기다린다 return Promise.all([getApple(), getBanana()]).then(fruits => { return fruits.join(` + `); }); // return Promise.all([getApple(), getBanana()]); } pickAllFruits().then(console.log); // 🍎 + 🍌
promise를 async & await으로 변환해야 할까?그렇지 않다. 프로미스 사용에는 두 방법이 있다.
엘리님에 의하면, 프로젝트를 많이 해보며 두 방법의 차이점에 대해 감을 잡아야 한다고...
콜백 지옥도 지옥인데.. 프로미스도 지옥이고... async도 지옥이고 ..😭😭