비동기 프로그래밍에 대한 깊은 이해는 JavaScript 개발자에게 중요한 역량 중 하나입니다. 이는 코드를 더 효과적으로 작성하고, 복잡한 문제를 해결하며, 성능과 사용자 경험을 개선하는 데 필요한 기술입니다.
const myPromise = new Promise((resolve, reject) => resolve(리졸브 해야하는 콜백함수))
const url = 'http://api.coinpaprika.com/v1/coins';
fetch(url); // Promise { <pending> } 프로미스 객체 리턴
fetch(url)
.then((res) => res.json()) // 프로미스 객체를 json()으로 사용할 수 있게 함
.then((data) =>
console.log(data) // {데이터 객체}
);
//즉 then의 res에는 fetch(url)의 리턴값인 프로미스 객체를 파라미터로 받는다
New Promise()
를 하게 되면 즉시 Promise에 전달 된 executor 함수가 실행된다.Promise.all()
을 자주 사용한다.const myAsync = async () => {
return 3;
}
console.log(myAsync()); // Promise { 3 }
console.log(myAsync().then(console.log)); // Promise { <pending> } // then으로 인해 3을 리턴하게 된다
const url = 'https://api.coinpaprika.com/v1/coins';
const url2 = 'https://api/coinpaprika.com/v1/coins/btc-bitcoin';
// 2개 이상의 url에 fetch 해야할 때
// 1. fetch 사용
// fetch(url1).then().then();
// fetch(url2).then().then();
//2. async await 사용 (즉시 실행함수)
(async() => {
// 불편한 통신
// const res1 = await fetch(url1);
// const res2 = await fetch(url2); // 문제점 : await가 걸려있어서 res1의 data가 fetch 될때까지 res2가 실행되지 않음
// 2개 url의 fetch를 동시에 할 수 있음
const [res1, res2] = await Promise.all([fetch(url1).then((res) => res.json()), fetch(url2).then((res) => res.json())]);
})()
Promise.all()
을 이용해서 배열화 시킨다.Thread란? 일 처리를 하는 하나의 Line이다.
서버와의 통신(네트워크 작업)이 가장 큰 요인이다.
비동기로 처리를 하게 되면 기다리지 않아도 된다!!
처음 들어온 작업을 다른 쓰레드에서 하도록 시킨다. 이 때 Pending이라는 상태값으로 Promise를 바로 리턴을 시키고, 그 작업이 끝나길 기다리지 않고 다음 작업을 진행한다.
예시 : 배달의 민족 주문시 발생
- 접수 대기 중을 띄워주며 접수를 받았을 때 즉시
Promise
를 던져준다. ex) 예상 배달시간 xx분 (pending)- 접수를 받지 않았을 때
reject
를 던져준다. ex) 배달 주문이 취소되었습니다. (reject)- 모든 프로세스가 완료 되었을 때 내가 시킨 음식을
resolve
해준다. (resolve/fulfilled)자주 실수하는 내용 : 밥이 아직 안왔는데 밥을 먹는다 라는 명령을 시킬 때가 있음
const getMeal = async () => {
return axios.get('https://배달의민족주문').then(res => res.data)
}
const eatMeal = (meal) => {
// do something
}
// 실수
eatMeal(getMeal()) // <Promise>를 meal에 던지고 있다 (밥이 아직 안오는데 먹으라 함)
// 정답
getMeal().then(meal => eatMeal(meal));
getMeal().then(eatMeal) // 보내는 인자와 받는 인자가 같을 때는 생략이 가능
작업을 다른 쓰레드에서 하도록 시킨 후, 그 작업이 끝나기를 기다렸다가 다음 작업을 진행한다.
순서가 중요한 작업
을 처리할 때 사용then
chaining 사용하는 것 : 직렬성 처리라고 볼 수 있다각자 독립적이지만 유사한 여러개의 작업
을 처리할 때 사용fetch
메소드를 사용하는것 : 동시성 처리라고 볼 수 있다분산 처리 시 동시처리가 더 좋아 보이는데 왜 직렬처리가 필요한가?
작업에 순서가 필요할 수 도 있기 때문이다
Main Thread
가 주체이고Thread
가 주체이다.엄연히 다른 Thread임
메인 스레드가 하위 스레드들을 관리하는 역할을 할 수 있다.
전체적 흐름 : 비동기로 작업 처리 요청을 하면 > Callback(이거 했을때 이거 해줘) > 너무 길어지면 보기가 힘들어짐 > Promise(데이터 준다고 약속할게, 성공/실패 가능)가 생김 > asnyc (return 값으로 Promise 줄게) > await (async function 내에서 이거 될 때 까지 내 밑에서 기다려! 하고 싶을때)
getUser('a01043340999@gmail.com', function(user) {
getPosts(user.id, function(posts) {
getComments(posts[0].id, function(comments) {
console.log(comments);
}, function(error) {
console.error("Error:", error);
});
}, function(error) {
console.error("Error:", error);
});
}, function(error) {
console.error("Error:", error);
});
Callback이 너무 심하게 들어가는 현상을 방지하기 위해 나온 Promise
State
:: process 실행 중(pending) or 성공(fulfilled) or 실패(reject)Producer
, Consumer
:: 정보 제공자와 사용자
pending
=>fullfilled
||rejected
const Promise = new Promise()
새로운 Promise가 만들어 질 때에는 우리가 생성한 executor 함수가 바로 실행된다.
// fetch는 어차피 Promise를 반환하기 때문에 따로 Promise를 만들어 줄 필요가 없기 때문에
// 불필요한 코드지만 예시를 위해 사용했습니다.
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))
.catch
를 이용하여 reject된 정보를 가져온다..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('아 시원하다 끝났다')); //reject resolve 결과와 상관없음
then으로 계속 직렬처리 방식으로 처리됨
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)
.catch(err => '에러임다~~');
// 에러가 났을 경우 다음과 같이 핸들링 :: 🧐 => 🍳
💡 Promise Chain을 더 간결하고 동기적으로 실행되는 것 처럼 보이게 만들어 주고 싶을 때 사용
상황에 맞게 Promise와 적절히 섞어 사용하는 것이 좋다.
동기적으로 만들때 자주하는 실수
const fetchCoins = () => {
return fetch('https://api.coinpaprika.com/v1/coins').then((res) => res.json());
};
const data = fetchCoins();
console.log(data); // undefined
이 때 네트워크 패널에서는 데이터가 페칭 되어있다.
해결방법 : 함수 앞에 async를 붙히자
const fetchCoins = async () => { return fetch('https://api.coinpaprika.com/v1/coins').then((res) => res.json()); } const data = fetchCoins(); console.log(data); // Promise 객체 반환
async를 함수 앞에 붙혀주면 자동으로 해당 함수는 Promise의 resolve를 뱉어내는 함수가 된다.
async가 붙은 함수 안에서만 쓸 수 있다.
const fetchCoins = async () => {
const data = await fetch('https://api.coinpaprika.com/v1/coins').then((res) =>
res.json()
);
return data;
};
fetchCoins().then(console.log); // async 함수는 Promise를 리턴한다 (.then 처리 필요)
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);
먼저 Before function
이 콘솔창에 출력되고 myFunc()가 콜스택에 쌓인다. myFunc()내의 In function!
이 이때 출력된다.
myFunc()은 async 함수이므로 await 를 만나고 queue로 빠지게 된다. (queue는 콜스택이 비어있을때만 들어온다)
queue는 콜스택이 비어있을때만 콜스택으로 들어오기 때문에 myFunc()바깥의 console.log('After function')
을 콜스택에 먼저 올리고 빠져 나간다.
콜스택이 완벽히 비어있으므로 queue에 있던 myFunc()을 콜스택으로 올린다. one()을 받고 console.log(res)
로 빠져나간다.
await를 사용하면 순차적으로 진행할 때 비효율적일 수 있다.
위의 예시에서 coins 의 정보를 받아오는 것과 overview 정보를 받아오는 것이 직렬적으로 처리 되고 있고, 둘은 서로 동시성
으로 처리 해도 되는 것이기 때문에 비효율적이다.
2개의 url 직렬처리가 되어 있는 코드 >> 순서에 상관없이 독립적으로 일어나는 일들은 동시성으로 처리하면 좋아 보임
동시성
으로 fetch 되게 하고 그 내용을 await 하여 병렬처리const fetchCoins = async () => {
return fetch(baseUrl).then((res) => res.json());
}
const fetchMarketOverview = async () => {
return fetch(overviewUrl).then((res) => res.json());
}
const getAll = async () => {
const coinsPromise = fetchCoins();
const overviewPromise = fetchMarketOverview();
const coins = await coinsPromise;
const overview = await overviewPromise;
return {coins, overview}
}
Promise.all([비동기작업1, 비동기작업2, ...])
사용const getCoins = fetch(baseUrl).then(res => res.json()).then((data) => data.slice(0,3));
const getOverview = fetch(overviewUrl).then(res => res.json());
const getAll = async () => {
const [coins, overview] = await Promise.all([getCoins, getOverview ]); // 구조분해 할당
console.log(coins);
}
Promise.race
const pickOnlyOne = () => {
return Promise.race([fetchCoins(), fetchMarketOverview()]).then(
(data) => data
);
};