[29-1] 이벤트 루프 (Event Loop)
[29-2] Callback과 비동기 처리
[29-2] 매크로 태스크 큐 vs 마이크로 태스크 큐
📂 시간을 화면에 보여줄 때 발생할 수 있는 이슈
// 싸이월드때 했던 내용 복습 // setInterval(() => { // document.getElementById("timer")?.innerText = "2:59"; // }, 1000); export default function TastQueuePage(){ const onClickTimer = () => { console.log("=======시작~~=======") setTimeout(() => { console.log("1초 뒤에 실행된답니다 😎️") }, 1000) console.log("=======끝~~=======") } return <button onClick={onClickTimer}>시작~~</button>; }
실행 결과
📂 태스크 큐 (Task Queue)
export default function TastQueuePage(){ const onClickTimer = () => { console.log("=======시작~~=======") setTimeout(() => { console.log("1초 뒤에 실행된답니다 😎️") }, 1000) console.log("=======끝~~=======") } return <button onClick={onClickTimer}>시작</button>; }
1️⃣ callStack에서 onClickTimer 함수가 실행된다. (Stack - Last In First Out / LIFO 구조)
2️⃣ Background에 setTimeout()을 보내서 실행한다.
3️⃣ setTimeout()이 TaskQueue로 전달되어 쌓인다. (Queue - First In First Out / FIFO 구조)
4️⃣ TaskQueue에 쌓이는 함수는 CallStack이 다 비워진 다음 가장 마지막에 실행된다.
스레드(Thread)
: TaskQueue에 있는 함수를 CallStack으로 보내는 역할
📂 싱글 스레드
비동기 작업
: setInterval, setTimeout처럼 CallStack에 쌓이지 않고 Background, TaskQueue로 넘겨지는 작업
📂 프로세스와 스레드
프로세스
: 실행되어있는 프로그램
스레드
: 프로세스 안에서 동작하는 일꾼
💡 싱글 스레드 언어 VS 멀티 스레드 언어
자바스크립트
: 싱글 스레드 언어
자바
,파이썬
: 멀티 스레드 언어
🎯 멀티 스레드가 싱글 스레드보다 좋은 것 아닌가?
반드시 그런 것은 아니다.
- 멀티 스레드 언어의 경우 동시에 여러가지 작업을 처리하고 있는 것처럼 보이지만, 사실은 여러 개의 스레드가 여러가지 작업을 번갈아가며 빠르게 수행하고 있는 것과 같다.
- 멀티 스레드의 경우에도 하나의 요청에 대한 응답을 기다렸다가 다음 작업으로 이동해야 하는 것은 동일하다. (이러한 작업을
컨텍스트 스위칭
이라고 한다.)
- 그렇기 때문에 멀티 스레드라고 해서 싱글 스레드보다 두드러지게 빠르지는 않다.
- 오히려 자바스크립트와 같은
이벤트 루프 싱글 스레드
의 경우 오래 걸리는 작업을 TaskQueue로 빼서 처리하기 때문에 높은 퍼포먼스를 낼 수 있다.
Callback 함수
: 함수의 인자로 들어가는 함수function aaa(qqq){ // 함수 로직 } aaa(function(){})
aaa(() => {})
📂 callback 함수를 왜 사용하는 걸까?
function aaa(qqq){ // 외부 API에 데이터 요청하는 로직 // ... // ... // 요청 끝! const result = "요청으로 받아온 데이터 결과값" qqq(result) // 요청 끝나면 qqq 실행시키기 } aaa(result) => { console.log("요청이 끝났습니다.") console.log("요청으로 받아온 데이터는" + result + "입니다") }
📂 비동기 실습( callback-promise-async/await )
📌 Callback
const onClickCallback = () => { // 첫번째 랜덤숫자 api 요청 const aaa = new XMLHttpRequest(); aa.open("get", "http://numbersapi.com/random?min=1&max=200"); aa.send(); aa.addEventListener("load", (res: any) => { const num = res.target.response.split(" ")[0]; // 두번째 posts api 요청 const bbb = new XMLHttpRequest(); bb.open("get", `https://koreanjson.com/posts/${num}`); bb.send(); bb.addEventListener("load", (res: any) => { const userId = JSON.parse(res.target.response).UserId; // 세번째 UserId api 요청 const ccc = new XMLHttpRequest(); cc.open("get", `https://koreanjson.com/posts?userId=${userId}`); cc.send(); cc.addEventListener("load", (res: any) => { console.log(res) console.log("최종 결과값 !!!!!!!!!"); console.log(JSON.parse(res.target.response)); }); }); }); };
🎯 콜백 지옥 (Callback Hell)
지금은 총 3회의 요청만 들어갔지만, API 요청이 2~3번 정도만 더 중첩되어도 코드의 가독성이 심각하게 떨어지게 된다.
📌 Promise
const onClickPromise = () => { axios .get("http://numbersapi.com/random?min=1&max=200") .then((res) => { const num = res.data.split(" ")[0]; return axios.get(`https://koreanjson.com/posts/${num}`); }) .then((res) => { const userId = res.data.UserId; // prettier-ignore return axios.get(`https://koreanjson.com/posts?userId=${userId}`) }) .then((res) => { console.log(res.data); }); };
💡 axios를 사용하는 이유
- promise란 자바스크립트의 비동기 처리, 그 중에서도 특히 외부에서 많은 양의 데이터를 불러오는 작업에 사용되는 객체다.
- promise 객체를 이용해서 만든 라이브러리가 axios
- axios 뿐만 아니라 데이터 통신에 사용되는 현대 라이브러리들은 대부분 Promise를 기반으로 만들어져 있다.
프로미스 체인(Promise chain)
또는 프로미스 체이닝(Promise chaining)
이라고 부른다.🎯 Promise가 직관적이지 못한 이유
const onClickPromise = () => { console.log("여기는 1번입니다~"); axios .get("http://numbersapi.com/random?min=1&max=200") .then((res) => { console.log("여기는 2번입니다~"); const num = res.data.split(" ")[0]; return axios.get(`https://koreanjson.com/posts/${num}`); }) .then((res) => { console.log("여기는 3번입니다~"); const userId = res.data.UserId; // prettier-ignore return axios.get(`https://koreanjson.com/posts?userId=${userId}`) }) .then((res) => { console.log("여기는 4번입니다~"); console.log(res.data); }); console.log("여기는 5번입니다~"); };
- axios를 이용한 비동기 작업이 TaskQueue 안에 들어가, 실행 순서가 뒤로 밀렸기 때문이다.
📌 Async/Await
const onClickAsyncAwait = async () => { console.log("여기는 1번입니다~"); // prettier-ignore const res1 = await axios.get("http://numbersapi.com/random?min=1&max=200"); const num = res1.data.split(" ")[0]; console.log("여기는 2번입니다~"); const res2 = await axios.get(`https://koreanjson.com/posts/${num}`); const userId = res2.data.UserId; console.log("여기는 3번입니다~"); // prettier-ignore const res3 = await axios.get(`https://koreanjson.com/posts?userId=${userId}`) console.log(res3.data); console.log("여기는 4번입니다~"); };
매크로 태스크 큐 (MacroTaskQueue)
setTimeout, setInterval 등이 들어가는 큐
마이크로 태스크 큐 (MicroTaskQueue)
Promise 등이 들어가는 큐
💡 태스크 큐들 간의 실행 우선순위
- 매크로 태스크 큐와 마이크로 태스크 큐가 부딪힐 경우에는
마이크로 태스크 큐가 우선순위
를 가져온다.
export default function EventLoopWithQueuePage() { const onClickTimer = () => { console.log("===============시작~~==============="); // 비동기 작업 (매크로큐에 들어감) setTimeout(() => { console.log("저는 setTimeout! 매크로 큐!! 0초 뒤에 실행될 거예요!!!"); }, 0); new Promise((resolve) => { resolve("철수"); }).then((res) => { console.log("저는 Promise! - 1!! 마이크로 큐!! 0초 뒤에 실행될 거예요!!!"); }); // 비동기 작업 (매크로큐에 들어감) setInterval(() => { console.log("저는 setInterval! 매크로 큐!! 1초 마다 계속 실행될 거예요!!!"); }, 1000); let sum = 0; for (let i = 0; i <= 9000000000; i += 1) { sum = sum + 1; } new Promise((resolve) => { resolve("철수"); }).then((res) => { console.log("저는 Promise! - 2!! 마이크로 큐!! 0초 뒤에 실행될 거예요!!!"); }); console.log("===============끝~~==============="); }; return <button onClick={onClickTimer}>시작</button>; }