callback, promise, async/await 패턴으로 변화하는 과정 실습하면서 포스팅.
세 개의 콜백함수로 이루어진 형태.
만약 콜백함수가 5개, 8개가 된다면? 생각만해도 골치아픔..
에러 처리도 각각 해줘야 되어 더욱 복잡해진다.
function findAndSaveUser(Users) {
User.findOne({}, (err, user) => {
if (err) {
return console.log(err);
}
user.name = 'seol';
user.save((err) => {
if (err) {
return console.log(err);
}
User.findOne({gender: 'm'}, (err, user) => {
console.log(user);
});
});
});
}
promise 객체는 일단 호출해놓고, 필요할 때 결괏값을 .then으로 꺼내 쓸 수 있음.
에러처리를 .catch에서 한번에 할 수 있음.
아래의 예제는 findOne과 save메서드가 Promise객체를 반환한다고 가정했을때 가능한 리팩터링이다.
function findAndSaveUser(Users) {
Users.findOne({})
.then((user) => { // user는 Users.findOne()에서 반환된 Promise 객체의 resolve()에 담긴 매개변수
user.name='seol';
return user.save();
})
.then((user) => { // user는 user.save()에서 반환된 Promise
return Users.findOne({ gender: 'm' });
})
.then((user) => {
console.log(user)
})
.catch(err => {
console.error(err);
});
}
async/await is syntactic sugar for promise chain
코드가 많이 짧아졌지만 try, catch문 오류처리는 꼭 해줘야 한다.
async로 선언된 함수의 반환값은 항상 promise가 된다.
스트링을 리턴해주더라도 결국 resolve('return value');
식으로 promise 객체를 리턴해 준것과 같다.
async function findAndSaveUser(Users) {
try {
let user = await Users.findOne({});
user = await user.save();
user = await Users.findOne({ gender: 'm' });
console.log(user);
} catch (err){
console.log(err);
}
}
아래의 두 함수의 리턴 값은 동일.
function fetchUser() {
return new Promise((resolve, reject) => {
// do network req for 10 secs...
resolve('your username is seol');
});
}
async function fetchUser2() {
return 'your username is seol';
}
아래는 드림코딩 유튭에서 학습한 코드
.then 체이닝을 async await 활용하여 간결하게 표현 가능하고,
promise.all([array of promises])
로 동시적 실행도 가능하다.
promise.all은 꼭 숙지해야함, promise.race도 있다. 무슨 뜻인지는 알제?
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
} // reusable delay function
async function getBanana() {
await delay(3000);
return 'banana'
}
async function getApple() {
await delay(3000);
return 'apple'
}
function pickFruits() {
return getApple().then(apple => {
return getBanana().then(banana => {
return apple + ' ' + banana;
});
});
}
pickFruits().then(console.log) // .then에 인자 없이 함수만 이름만 넘기면 기본인자를 소비하는 형태로 함수를 호출한다. 아래와 동일한 함수임
pickFruites().then(data => { console.log(data) });
// refactor pickFruits with async/await
async function pickFruitsConcise() {
const apple = await getApple();
const banana = await getBanana();
return apple + ' ' + banana
} // still it takes 3 + 3 seconds to run
// if you want to run getApple(), getBanana() concurrently use below
async function pickFruitsConcurrent1() {
const applePromise = getApple();
const bananaPromise = getBanana();
// wait for above promises here
const apple = await applePromise;
const banana = await bananaPromise;
return apple + ' ' + banana
}
// Promise.all makes above code much more prettier!!!
async function pickFruitsConcurrent2() {
return Promise.all([getApple(), getBanana()])
.then(fruits => fruits.join(' '));
}
pickFruitsConcurrent2.then(console.log);
juiceMap의 값은은 getFruits에서 반환한 promise가 resolved된 결과가 emoji에 변수에 저장되는 것 처럼 보이지만 그렇지 않고 pending한 promise가 바로 저장된다.
juiceFor처럼 활용해야 의도한 값이 나온다.
만약 전체를 병렬적으로 수행하려면 juiceParallel 처럼 await의 위치를 for 옆에 넣으면 된다.
const fruits = {
pineapple: "🍍",
peach: "🍑",
banana: "🍌",
};
const fruitsArr = ['peach', 'pineapple', 'banana']
async function getFruits(name) {
console.log('getFruits', name, fruits[name]);
return fruits[name];
}
const juiceMap = fruitsArr.map(async f => {
const emoji = await getFruits(f);
console.log(emoji);
return emoji;
});
console.log(juiceMap); // [ Promise { <pending> }, Promise { <pending> }, Promise { <pending> } ]
const juiceFor = async () => {
for (const f of fruitsArr) {
const emoji = await getFruits(f);
console.log(f, emoji);
}
};
juice2();
const juiceForParallel = async () => {
for await (const f of fruitsArr) {
const emoji = getFruits(f);
console.log(f, emoji);
}
};