비동기 프로그래밍에 대한 깊은 이해는 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.raceconst pickOnlyOne = () => {
return Promise.race([fetchCoins(), fetchMarketOverview()]).then(
(data) => data
);
};