JavaScript async, await 개념과 활용에 대해 공부함
그러나 무조건 promise가 나쁘고 async, await을 대체해서 사용해야한다 라는 것은 아님. promise를 유지해서 써야할 때와 async로 바꿔 써야 더 깔끔해지는 경우가 있을 뿐
이런 차이점은 프로젝트를 해보면서 감을 찾아가는 것이 좋음
function fetchUser() {
// do network reqeust in 10 secs...
return 'heechan'
}
const user = fetchUser();
console.log(user); // heechan
다음과 같이 사용자의 데이터를 백엔드에서 받아오는데 10초가 걸리는 함수가 있다고 가정
이렇게 오래 걸리는 코드를 비동기적인 처리를 하지 않으면 자바스크립트 엔진은 동기적으로 수행하기 때문에 즉, 한줄이 끝나야 그 다음줄로 넘어가는 동기적인 처리를 하기 때문에
1. const user = fetchUser(); : 함수가 호출되었네?
2. function fetchUser() { return . . . } : 함수의 코드 블록 실행
3. do network reqeust in 10 secs... : 어? 10초 걸리네? 10초동안 기다려야지...
4. 10초 후 데이터를 성공적으로 받았다! 이제 return으로 넘어가야지!
5. return 된 'heechan'이 user에 할당되고 console.log로 출력됨
여기서 비동기 처리를 전혀 하지 않으면 사용자의 데이터를 받아오는데 10초가 걸리기 때문에 만약 이 뒤에 웹페이지의 UI에 받아온 데이터를 표시하는 기능을 수행하는 코드가 있다면 이게 끝나는 동안 데이터가 웹페이지에 표시되지 않기 때문에 사용자는 10초동안 비어있는 웹페이지만 보게 될 것임
이렇게 오래 걸리는 일들은 비동기적으로 처리할 수 있게 하는 것이 Promise Object.
function fetchUser() {
return new Promise((resolve, reject) => {
// do network reqeust in 10 secs...
resolve('heechan')
})
}
const user = fetchUser();
user.then(console.log) // heechan
Promise 가 말하기를 내가 언제 유저 데이터를 가져올지는 모르지만 일단 약속할게! 네가 이 Promise Object를 가지고 있고 then이라는 콜백함수만 등록해놓으면 유저데이터가 준비되는 대로 네가 등록한 콜백함수(then)를 불러줄게! 라고 한 상황이 위의 코드
Promise 내부에는 resolve, reject 콜백함수를 받는 생성자 executor 콜백함수를 만들었음. 그래서 코드 블록 안에 있는 코드가 비동기적으로 수행됨.
function fetchUser() {
return new Promise((resolve, reject) => {
// do network reqeust in 10 secs...
return 'heechan'
})
}
const user = fetchUser();
console.log(user);
> Promise {<pending>}
> [[Prototype]]: Promise
> [[PromiseState]]: "pending"
> [PromiseResult]]: undefined
만약 resolve, reject를 만들지 않고 바로 return 하면 promise pending 상태가 되어 있는 것을 볼 수 있음
function fetchUser() {
return new Promise((resolve, reject) => {
// do network reqeust in 10 secs...
resolve('heechan')
})
}
const user = fetchUser();
user.then(console.log)
> Promise {<fulfilled>: 'heechan'}
> [[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: "heechan"
resolve를 호출하면 fulfilled 상태가 되며 결과값도 생김
reject일시 rejected 상태
async function fetchUser() {
// do network reqeust in 10 secs...
return 'heechan'
}
const user = fetchUser();
user.then(console.log) // heechan
promise를 쓰지 않고 함수 앞에 async 키워드만 붙여주면 자동으로 코드 안의 블록들이 promise로 변환됨. 이것도 async가 promise를 감싸고 있는 형태이기 때문에 syntactic sugar 임
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function getApple() {
await delay(2000);
return '🍎';
}
async function getBanana() {
await delay(2000);
return '🍌';
}
- await 키워드는 async 키워드가 붙은 함수에서만 사용가능
delay 함수는 promise를 return하고 정해진 ms가 지나면 resolve를 호출하는 promise를 리턴한다.- await를 쓰면 2초가 걸리는 delay가 끝날 때까지 기다려줌
2초가 지나면 사과를 return 하는 promise가 만들어짐- getBanana()는 2초 후에 바나나를 return 하는 promise를 만드는 함수인데 이것을 promise로 굳이 바꿔보면 아래와 같음
function getBanana() {
return delay(3000)
.then(() => '🍌')
}
여기서 then이 어떤 값을 받았는지는 상관없고 결국 바나나를 return 함
이렇게 chaining 하는 것보다 async-await 를 써서 동기적인 코드를 쓰는 것처럼 만들게 되면 더 쉽게 이해가 됨
function pickFruits() {
return getApple()
.then(apple => {
return getBanana().then(banana => `${apple} + ${banana}`);
});
}
pickFruits().then(console.log);
이번에는 과일을 한꺼번에 따오는 pickFruits 함수를 기존의 promise를 써서 만들어봄.
이걸 보면 뭔가 떠오르는 것이 있지않나? 그 이름은 콜백지옥.
프로미스도 중첩적으로 체이닝을 하게 되면 콜백지옥과 비슷한 문제점이 발생
async function pickFruits() {
const apple = await getApple();
const banana = await getBanana();
return `${apple} + ${banana}`
}
pickFruits().then(console.log);
그러나 async & await 를 사용하면 간단해짐
async function getApple() {
await delay(2000);
throw 'error';
return '🍎';
}
async function getBanana() {
await delay(2000);
return '🍌';
}
async function pickFruits() {
try {
const apple = await getApple();
const banana = await getBanana();
return `${apple} + ${banana}`
} catch(error) {
console.log('에러남')
}
}
reject 대신 throw, then-catch 대신 try-catch를 사용
앞서 했던 코드에서 효율면에서 문제가 있는데 사과를 받는 시간이 2초, 바나나를 받는 시간이 2초가 걸려 총 4초가 소요됨. 그런데 사과와 바나나의 데이터를 받아오는 것은 둘이 서로 연관이 되어 있지 않기 때문에 서로 기다릴 필요가 없음
async function pickFruits() {
const applePromise = getApple();
const bananaPromise = getBanana();
const apple = await applePromise;
const banana = await bananaPromise;
return `${apple} + ${banana}`
}
pickFruits().then(console.log);
- getApple()과 getBanana()는 promise를 만들고나서 곧바로 병렬적으로 실행이 됨.
- await applePromise와 await bananaPromise로 동기화를 시키고 4초가 걸리는 작업이 2초만에 병렬적으로 실행됨
function pickAllFruits() {
return Promise.all([getApple(), getBanana()])
.then(fruits => fruits.join(' + '));
}
pickAllFruits().then(console.log)
Promise.all : 프로미스 배열을 전달하게 되면 모든 프로미스들이 병렬적으로 받을 때까지 모아주는 역할을 함.
배열형태로 getApple(), getBanana() 의 프로미스의 배열을 전달하게 되면 애네들이 다 받아지면 then 으로 다 받아진 배열이 전달이 됨. 즉 과일의 배열이 전달받아진다. 배열을 문자열로 묶을 수 있는건 join이 있었음.
function pickOnlyOne() {
return Promise.race([getApple(), getBanana()]);
}
pickOnlyOne().then(console.log)
Promise.race : 예를 들어 사과와 바나나를 받아오는 시간이 각각 2초가 아니라 사과 4초, 바나나 2초가 걸린다고 가정했을 때 가장 먼저 받아지는 바나나만을 출력하는 API
async, await은 promise를 좀 더 간편하게 쓸 수 있는 오브젝트이고 promise 에는 all, race 와 같은 유용한 API가 있다.
드림코딩 유튜브