Javascript 는 싱글쓰레드 이지만, 그래도 쓰레드의 개념을 알고 오면 좋다.
근데 이거 너무 길어지면 보기 힘드니까 Promise 가 생김
보통 setTimeout 을 사용하여 비동기처리 예시를 드는데 처음 배웠을 때 실제 사용방법과 약간의 차이점이 있어서 잘 이해하지 못했던 부분이 있었다.
그래서 여기서는 Coin paprika api 에서 coin 정보들을 fetch하는 것을 예시로 들 예정이다.
Callback
💡
정의 :: 함수에 파라미터로 들어가는 함수
용도 :: ...할 때! ⇒ 이거 해라 같이 순차적으로 실행하고 싶을 때 씀
Promise
Callback 이 너무 심하게 들어가는 걸 방지하기 위해 태어난 Promise
💡 두가지의 키워드!!
State
:: process 실행 중 || 성공 || 실패
Producer , Consumer
:: 정보 제공자와 사용자
pending
⇒ fulfilled
|| rejected
const promise = new Promise()
새로운 promise 가 만들어 질 때에는 우리가 생성한 executor 함수가 바로 실행된다.
const promise = new Promise((resolve, reject) => {
// 뭔가 헤비한 일들 (데이터를 가지고 오거나, 큰 데이터를 읽어올 때)
// resolve 안에 우리가 비동기적으로 해야 하는 일을 넣어준다.
resolve(fetch('https://api.coinpaprika.com/v1/coins').then(res=>res.json()));
});
promise.then((data) => console.log(data))
Producer
쪽에서는 에러가 발생 했을 때 해주고 싶은 일을 reject
에 적어주고
Consumer
쪽에서는 .catch
를 이용하여 reject
된 정보를 가져온다.
Consumer
의 .finally
는 resolve
되거나 reject
되거나에 상관 없이 실행된다.
const promise = new Promise((resolve, reject) => {
// 뭔가 헤비한 일들 (데이터를 가지고 오거나, 큰 데이터를 읽어올 때)
resolve(
fetch('https://api.coinpaprika.com/v1/coins').then((res) =>
res.json()
)
);
reject(new Error('no network'));
});
promise
.then((data) => console.log(data.slice(0, 100)))
.catch((err) => console.log(err))
.finally(() => console.log('아 시원하다 끝났다'));
fetchCoins
.then((data) => data.slice(0, 100))
.then((data) => data.map((item) => item.name))
.then(
(names) =>
new Promise((resolve, reject) => {
resolve(names.slice(0, 30));
})
)
.then((data) => console.log(data));
.then
에서는 값을 바로 전달해도 되고 새로운 promise
를 전달해도 된다.
const getHen = () =>
new Promise((resolve, reject) => {
setTimeout(() => resolve('🐓'), 500);
});
const getEgg = (hen) =>
new Promise((resolve, reject) => {
setTimeout(() => resolve(`${hen} => 🥚`), 500);
});
const cook = (egg) =>
new Promise((resolve, reject) => {
setTimeout(() => resolve(`${egg} => 🍳`), 800);
});
getHen()
.then(getEgg) // .then(hen => getEgg(hen))
.then(cook) // .then(egg => cook(egg))
.then(console.log); // .then(result => console.log(result))
// 이런식으로 하나의 인자가 들어가고 함수가 하나의 인자를 받으면 callback 함수 데려오기만 해도 됌
// 🐓 => 🥚 => 🍳
getHen() //
.then(getEgg)
.catch((err) => '🧐')
.then(cook)
.then(console.log);
// 에러가 났을 경우 다음과 같이 핸들링 :: 🧐 => 🍳
💡
Promise Chain 을 더 간결하고 동기적으로 실행되는 것처럼 보이게 만들어주고 싶었다.
깔끔하게 Promise 를 사용 할 수 있는 방법
단, async await 가 무조건 좋다 ❌
Promise 와 적절히 섞어 써야한다.
async
const fetchCoins = () => {
return fetch('https://api.coinpaprika.com/v1/coins').then((res) => res.json());
};
const data = fetchCoins();
console.log(data); // undefined
undefined
가 나온다.
그리고 network 패널에 들어가보면 데이터가 페칭되어 있다.
엥?? undefined 떳었잖아!!!
const fetchCoins = async () => {
return fetch('https://api.coinpaprika.com/v1/coins').then((res) =>
res.json()
);
};
const data = fetchCoins();
console.log(data);
콘솔을 찍어보면 Promise
가 나오고 result
에 return
된 값이 나온다.
💡
async
를 함수 앞에 붙혀주면 자동으로 해당 함수는Promise
의resolve
를 뱉어내는 함수가 된다.
await
💡
async 가 붙은 함수 안에서만 쓸 수 있다.
const fetchCoins = async () => {
const data = await fetch('https://api.coinpaprika.com/v1/coins').then((res) =>
res.json()
);
return data;
};
fetchCoins().then(console.log);
💡
await 의 강점은! promise 안에서의 콜백지옥 같은 녀석을 이쁘게 처리 해주는 것!
// await 를 쓰지 않고 Promise Chaining했을 경우
// 물론 Promise.all 을 써도 좋음 (사용 방법에 따른 차이)
const getAll = () => {
return fetchCoins().then((coins) => {
return fetchMarketOverview().then((global) => { coins, global });
});
};
getAll().then(console.log);
// await 를 사용 한 경우
const getAll = async () => {
const coins = await fetchCoins();
const overview = await fetchMarketOverview();
return { coins, overview };
};
getAll().then(console.log);
순차적으로 진행할 때 비효율 적일수 있다.
위의 예시에서 coins
의 정보를 받아오는 것과 overview
정보를 받아오는 것이 직렬적으로 처리 되고 있고,
둘은 서로 동시성
으로 처리 해도 되는 것이기 때문에 비효율적이다..
const getAll = async () => {
const coinsPromise = fetchCoins();
const overviewPromise = fetchMarketOverview();
const coins = await coinsPromise;
const overview = await overviewPromise;
return { coins, overview };
};
이런식으로 Promise 를 바로 만들어서 ‘동시성' 으로 fetch 되게 하고 그 내용을 await 하여
병렬 처리를 할 수 있다.
💡
그런데 이거 너무 지저분한데요??
⭐️ Promise.all 여러군데(여러 엔드포인트)에서 한번에 fetch 받아올 때 많이 씀
const getAll = () => {
return Promise.all([fetchCoins(), fetchMarketOverview()]).then((data) => {
const [coins, overview] = data;
return { coins, overview };
});
};
getAll().then(console.log);
Promise.race
먼저 전달된 내용만 가져가겠다.
const pickOnlyOne = () => {
return Promise.race([fetchCoins(), fetchMarketOverview()]).then(
(data) => data
);
};
리팩토링
const COIN_URL = 'https://api.coinpaprika.com/v1/coins';
const GLOBAL_URL = 'https://api.coinpaprika.com/v1/global';
const getData = async (opts) => {
const fetchData = await fetch(opts.url);
return fetchData.json();
};
const getAll = () => {
return Promise.all([getData({ url: COIN_URL }), getData({ url: GLOBAL_URL })]) //
.then((data) => {
const [coins, overview] = data;
return { coins, overview };
});
};
getAll().then(console.log);
Promise 핵심
- rule1. Promise를 쓴다는 말은 곧 Promise객체를 return 하는 것과 같은 말이다.
- rule2. then에 전달되는 함수가 받는 parameter는 resolve에 넣은 value가 전달된다.
- rule3. Promise를 리턴하는 함수를 실행하게 되면 리턴값은 당연히 Promise 객체가 즉시 리턴된다. 이 Promise 객체가 pending, fulfilled, rejected되는 것은 그 다음 문제이다.
- rule4. 중요: new Promise() 를 하게 되면 즉시 Promise에 전달 된 executor 함수가 실행된다.
- rule5. 마찬가지로, Promise를 리턴하는 함수를 실행하게 되면 바로 Promise에 전달 된 executor 함수가 즉시 실행된다.
- rule6. Promise chaining을 하게 되면 then()에서 return한 값을 그 다음 then()에 전달되는 함수(executor)의 parameter로 들어 가게 된다.
- rule7. Promise chaining을 할 때 then은 value를 바로 return해도 되며, promise를 return 해도 된다. value를 리턴 할 경우엔 즉시 그 다음 then에 전달한 function이 즉시 실행되며, promise를 return 할 경우 해당 promise가 resolve되면 그 다음 then이 실행된다.
- rule8. 동시에 여러 Promise를 실행 시킬 때는 Promise.all()을 자주 사용한다.
Async/Await 핵심
- rule1. async function을 만들면 javascript/typescript 내부적으로는 자동으로 Promise 객체를 리턴하게 된다. 즉, 자동으로 Promise를 return해 주게 된다.
- rule2. async function을 await 없이 실행하면 Promise를 return 한다. await가 없기 때문에 기다림 없이 Promise를 즉시 리턴받게 된다. (위 룰 4, 5와 동일)
- rule3. 위 rule2를 사용하는 사람도 있겠지만 왠만하면 Promise.all()을 이용해서 배열화 시킨다.