이벤트 루프 (Event Loop)


export default function TastQueuePage(){

	const onClickTimer = () => {

		console.log("=======시작~~=======")

		setTimeout(() => {
			console.log("0초 뒤에 실행된답니다 😎️")
		}, 0)

		console.log("=======끝~~=======")
	}

	return <button onClick={onClickTimer}>시작</button>;
}
  • 위와 같이 작성한 코드는 setTimeout 함수의 console.log("0초뒤에 실행된답니다") 가 가장 마지막에 실행됨
    *싱글 스레드인 자바스크립트의 스택과 큐 원리 때문!





  • 자바스크립트에서 실행되는 함수들은 Callstack 에 쌓이게 됨

  • 이 때 setTimeout 같은 일정 시간이 걸리는 함수들은 해당 함수가 실행 후 종료되기까지를 기다리지 않고 Taskqueue 로 넘겨버림 (비동기 처리)

  • 이후 Callstack의 모든 함수들을 처리한 이후 Taskqueue로 넘어간 함수들이 Callstack으로 넘어가서 처리 됨





+a) 싱글 스레드

  • 스레드란 작업을 실행하는 주체를 의미하며 자바스크립트는 싱글 스레드이기 때문에 한 번에 하나의 프로세스만 처리할 수 있음
    *즉 setTimeout 같은 함수를 실행되고 종료까지 기다리면 그동안 다른 작업을 못하기 때문에 우선 Queue로 보내 비동기처리하고 다른 작업들의 수행이 끝나면 처리되도록 하는 것 (효율적인 방식)


    싱글 스레드 = non-blocking 방식 (한 작업이 진행되는 동안 오래걸리는 작업은 따로 마련한 공간에 던져두고 나중에 실행)





+a) 멀티 스레드

  • 여러 개의 스레드가 여러가지 작업을 번갈아가면서 빠르게 수행

  • 비동기처리를 하지않고, 하나의 요청에 대한 응답을 기다렸다가 모두 처리된 이후 다음 작업으로 이동 (Context switching)


    멀티 스레드 = blocking 방식 (하나의 요청에 대한 응답이 와야지만 다음 작업을 수행가능)








Callback과 비동기 처리


function aaa(qqq){
	// 외부 API에 데이터 요청하는 로직
	// ...
	// ...
	// 요청 끝!
	const result = "요청으로 받아온 데이터 결과값"
	qqq(result) // 요청 끝나면 qqq 실행시키기
}

qqq(result) => {
	console.log("요청이 끝났습니다.")
	console.log("요청으로 받아온 데이터는" + result + "입니다")
}
  • Callback 함수 = 함수의 인자로 들어가는 함수

  • 위와 같이 특정 함수의 실행이 끝나고 해당 실행결과를 Callback 함수에 인자로 넣어서 Callback 함수를 실행하는 방식으로 사용
    *이런 방식으로 함수의 실행 결과를 기다리지 않고 이를 바로 Callback 함수에 인자로 넣어서 결과를 도출하여 비동기 처리






비동기 실습 (Callback, Promise, Async-Await)




Callback 실습


XMLHttpRequset (JS 에 내장되어 있는 기능)


- 서버와 상호작용하기 위해 사용되는 객체

- 페이지의 새로고침 없이 URL로부터 data를 받아올 수 있음



  • 새로운 XMLHttpRequest 를 생성하고, .open 으로 POST 방식과 URL 입력

  • .send 로 해당 URL 로 요청을 보냄

  • .addEventListener 를 이용해 요청결과가 load 되면 지정한 함수를 실행


  • 위 과정을 반복하면서 받아온 결과값을 다시 URL의 값으로 지정하여 요청을 보내고, 이에 대한 응답결과를 다시 URL의 값으로 지정해서 새로운 요청을 보냄
    *이 과정을 통해 각 함수의 요청에 대한 응답을 기다리지 않고 바로 다음으로 실행할 함수에서 또 다른 요청의 값으로 활용함으로써 비동기 처리 가능

    [BUT!] => 요청이 늘어날 수록 코드 형태가 대각선으로 점점 파이면서 가독성이 심각하게 떨어지게 됨! (콜백지옥)








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);
    });
};
  • 앞서 Callback의 문제를 해결하기 위해 Promise 를 사용할 수 있음

  • Promise 객체는 외부에서 data를 불러올 때 사용되는 객체로, 요청에 대한 응답 data가 비동기처리로 인해 제대로 전달되지 않았을 때 Promise 객체를 응답으로 전달함으로써 에러를 발생시키지 않게 됨

  • Promise 객체에는 Async/Await 을 붙일 수 있음 (다른 말로하면 Promise 객체에만 붙일 수 있음)

  • 위와 같이 .then( ) 을 이용해서 요청에 대한 응답이 들어오면 해당 응답 data (res)를 어떻게 처리할 건지를 설정할 수 있고, return 문을 이용해 다음 요청을 바로 보낼 수 있음


    *결과적으로 콜백지옥이 발생하지 않고, 코드의 가독성이 좋아짐





+a) Promise 객체에 사용할 수 있는 .then( ) 을 axios 에 사용할 수 있는 이유

  • axios 를 포함한 데이터 통신에 사용되는 현대의 라이브러리들은 대부분 Promise를 기반으로 만들어져 있음

  • 따라서 axios는 Promise를 기반으로 만들어졌기 때문에 .then( ) 같은 메소드 활용이 가능하며, async/await 도 사용가능






    [BUT!] => Promise 로도 해결되지 않는 문제가 있음!!



Promise의 문제점 2가지


1. 결과값을 상수에 바로 담지 못함

const onClickPromise = () => {
  const result = 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);
    });
	console.log(result)
};



  • 위와 같이 코드를 작성하여 요청을 보내고, 이 자체를 result에 담으면 각 요청에 대해 비동기처리가 되어 응답을 받기도 전에 Promise 객체가 반환되기 때문에 pending 상태로 promise 객체 응답을 받게 됨 (결과값을 완전한 data로 받지 못함)







2. 코드 실행순서가 직관적이지 못함

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번입니다~");
};
  • 위와 같은 코드가 실행될 경우 코드의 순서대로 실행되지 않음
    *자바스크립트 특성 상 오래걸리는 작업 (ex.데이터 요청) 은 queue에 들어가서 비동기 작업으로 처리되기 때문에 순서대로 처리되지 않음!









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번입니다~");
};
  • 위의 Promise의 문제를 해결하기 위해 Async/Await 방식이 탄생함

  • 코드를 읽어가는 과정에서 await을 만나는 순간 해당 await이 속한 async 함수 자체가 queue에 들어가게 됨
    *정확히는 await으로 기다릴 API 요청 (여기서는 axios.get~) 이 queue에 먼저 담기고, 이후 async 함수가 queue에 담기게 됨

  • 요청에 대한 응답을 기다린 이후 다음 코드가 실행되기 때문에 코드 상의 순서대로 실행이 됨







MacroTaskQueue vs MicroTaskQueue



MacroTaskQueue = setTimeout, setInterval 등이 들어가는 queue


MicroTaskQueue = Promise 등이 들어가는 queue






태스크 큐들 간의 실행 우선순위



  • 비동기 처리로 Queue로 들어가는 것들은 다시 MacroqueueMicroqueue 로 나눠져서 들어가게 됨

  • 여기서 모든 stack이 빠져나가면 queue에서 처리되는 것들 중 Microqueue에 있는 것이 Stack으로 빠져서 우선 실행됨








Await 과 Microqueue 의 관계

import axoios from 'axios'

export default function IsSubmitting(){
	const [isSubmitting,setIsSubmitting] = useState(false)

	const onClickSubmit = async ()=>{
		// 등록하는 동안은 등록버튼이 작동하지 않도록
		setIsSubmitting(true)

		const result = await axios.get("https://koreanjson.com/posts/1")
		
		// 등록이 완료되었다면 다시 버튼이 동작하도록
		setIsSubmitting(false)

	}
	
	return(
		<button onClick={onClickSubmit} disabled={isSubmitting}> 등록하기 등의 API 요청버튼 </button>
	) 
}
  • 위와 같은 코드가 실행된다면, state는 변경값이 임시저장소(prev)에 들어갔다가 함수가 종료된 이후 실질적으로 바뀌기 때문에 여기서 isSubmittg 은 값이 바뀐 적이 없어야 함


  • 그러나 실제로 실행해 보면 true로 바뀌었다가 다시 false로 바뀜




[이유]

-1. 코드를 읽어내려가는 과정에서 await을 만나는 순간 해당 await을 감싸는 async 함수 전체가 Microqueue에 들어가게 됨


-2. 여기서 함수는 한 번 종료되기 때문에 state의 값이 true로 바뀌게 됨


-3. 이후 stack이 모두 비어진 상태가 되면, Microqueue에 있는 await으로 기다림 되고 있는 요청처리가 먼저 실행되고, 이후 async 함수가 실행됨
(여기서 async는 종료된 이후 부분부터 읽어내려감)


-4. async 함수가 처리되고 종료되면 다시 한 번 state의 값이 false로 바뀜

profile
막 발걸음을 뗀 신입

0개의 댓글