TIL[30일차]자바스크립트 기본기, 비동기 처리 방법 - async/await 이전에 콜백함수와 promise가 있었다!

남예지·2022년 12월 9일
0

TIL

목록 보기
24/47
post-thumbnail

시작하기전에..
async/await는 최신기능이다. 이게 없었을 때는 프로미스와 콜백함수를 사용했다. 이처럼 새로운 기술이 나왔다면 똑같은 기능을 하던게 뭐였는지. 뭐가 개선이 되었는지 알아보아야 성장을 빠르게 할 수 있다!!

Callback과 비동기 처리

함수를 실행할 때 매개변수로 숫자나 문자만 넣는게 아니라 함수도 넣을 수 있다!

함수에 인자로 들어가는 함수를 콜백함수라고 한다.

aaa(() => {})

aaa야 결과 받아오면 결과 값으로 실행해줘
즉 함수 안에서 그 함수의 요청이 끝나서 그 결과 값을 가지고 다른 요청을 실행시켜야 할 때 콜백함수를 사용해 요청을 실행할 수 있다.

await를 console.log() 위에 넣는게 좋다.

new XMLHttpRequest()

Callback함수 내부에서는 XMLHttpRequest() 객체를 이용해서 데이터를 읽어올 수 있다.
XMLHttpRequest() 란?
서버와 상호 작용하기 위해 사용되는 객체로 전체 페이지의 새로고침 없이도 URL로부터 데이터를 받아올 수 있고, 주로 Ajax 프로그래밍에 주로 사용되며, XHR이라고 줄여 부르기도 한다.

const myCallback = () => {
                const aa = new XMLHttpRequest();
                aa.open("get", `http://numbersapi.com/random?min=1&max=200`);
                aa.send();
                aa.addEventListener("load", () => {
                    console.log(res); // "철수" API 요청 결과
                    const num = res.target.response.split(" ")[0]; // 171(랜덤숫자)

                    const bb = new XMLHttpRequest();
                    bb.open("get", `https://koreanjson.com/posts/${num}`);
                    bb.sent();
                    bb.addEventListener("load", (res) => {
                        console.log(res); //  API 요청 결과
                        // 문자열을 객체형태로 JSON.parse()
                        const UserId = JSON.parse(res.target.response).UserId;

                        const cc = new XMLHttpRequest();
                        cc.open(
                            "get",
                            `https://koreanjson.com/posts?userId=${UserId}`
                        );
                        cc.send();
                        cc.addEventListener("load", (res) => {
                            console.log(res); // 최종 API 요청 결과
                        });
                    });
                });
            };

랜덤수에 해당하는 게시글 데이터가 response에 문자열로 들어오는데 이때문에 가공하기 어렵다. 그래서 JSON.parse()를 이용해 문자열을 객체로 바꿔주고, 해당 데이터 작성자 정보를(UserId)를 이용해서 세 번째 요청을 보내준다.
잘 실행되지만 실행된 코드의 모양이 점점 안으로 들어가는걸 볼 수 있다.
이처럼 콜백함수는 콜백안에 콜백함수가 들어가서 유지보수가 힘들다.
콜백지옥이라고 한다
이런 문제를 개선하기 위해 promise라는 게 나왔다.


Promise

new Promise((성공했을때함수, 실패했을때함수) =>{
                try{
                    // 여기서 API 요청
                    성공했을때함수(response)
                    const response = " 철수"
                } catch(error){
                    실패했을때함수("실패했습니다")
                }
            }).then(() => {
                // 철수
            }).catch(()=>{
                // 실패했습니다!!!
            })

둘 중 하나가 실행되어야 끝난다.

.then(() => {}).catch(()=>{})

성공시 then이 실행되고 실패했을때는 catch가 실행된다.

리턴하면 그 뒤에 then을 아래로 붙일수 있다는 규칙이 있다.
이걸 프로미스 체이닝이라 한다.
이렇게 콜백지옥을 없앴다.

그런데! 원하는데로 실행되지 않는다.
저번 시간에 배운 setTimeout처럼 큐에 들어가있기 때문이다.
그리고 promise는 이를 판단하기엔 코드 가독성이 좋지않다.
좀 더 좋은 방법 없을까?


async-await

최신기능
없을때는 프로미스와 콜백함수를 사용했다.

프로미스의 문제를 개선한 await는 아래와 같이 사용한다.

const myAsyncAwait = async () => {
                await axios.get(`http://numbersapi.com/random?min=1&max=200`);
                await axios.get(`http://numbersapi.com/random?min=1&max=200`);
                await axios.get(`http://numbersapi.com/random?min=1&max=200`);
            };

깔끔하고 순서대로 실행된다.

await를 붙이고 싶은데 붙인다고 해서 다 기다리는 건 아니다.
await는 promise앞에 붙일 수 있다.
.get이나 .then을 붙일수 있는 promise!!

대표적인 Promise(.then, .catch 등의 기능)를 지원하는 기능이 axios, fetch등 이다.
axios, fetch등을 기다리는 2가지 방법이 있다.
1. .then() 활용
2. await활용 => (주의) await는 아무데나 붙인다고 뒤에껄 기다리는게 아님!


나만의 axios 만들기

매크로 태스크 큐 / 마이크로 태스크 큐

Promise로 실행하는 함수는 태스크 큐에 들어간다고 했는데 태스크큐는 여러개가 동시에 존재하는데 대표적인 두 가지가 매크로 태스크 큐

실행의 우선순위가 다르다.
매크로 테스크큐 : setTimeout, setInterval
마이크로 테스크큐 : Promise
마이크로 테스크큐에 먼저 가고 나중에 매크로 테스크큐에 간다.

-실습

<!DOCTYPE html>
<html lang="ko">
  <head>
    <title>eventloop</title>
    <script>
      const onClickLoop = () => {
        console.log("시작~~~!");

        // 비동기작업(매크로큐에 들어감)
        setTimeout(() => {
          // 비동기작업(마이크로큐에 들어감)
          new Promise((resolve) => resolve("철수")).then(() => {
            console.log("저는 Promise(setTimeout 안에서 실행될거에요!)에요");
          });

          console.log("저는 setTimeout!! 매크로큐 !! 0초 뒤에 실행될 거에요!!");
        }, 0);

        // 비동기작업(마이크로큐에 들어감)
        new Promise((resolve) => resolve("철수")).then(() => {
          console.log(
            "저는 Promise(1)에요! 마이크로큐!! 0초 뒤에 실행될 거에요!!"
          );
        });

        // 비동기작업(매크로큐에 들어감)
        setInterval(() => {
          console.log("저는 setInterval!! 매크로큐!! 0초마다 실행될거에요!!");
        }, 0);

        let sum = 0;
        for (let i = 0; i <= 9000000000; i++) {
          sum += 1;
        }

        // 비동기작업(마이크로큐에 들어감)
        new Promise((resolve) => resolve("철수")).then(() => {
          console.log(
            "저는 Promise(2)에요! 마이크로큐!! 0초 뒤에 실행될 거에요!!"
          );
        });

        console.log("끝~~!");
      };
    </script>
  </head>
  <body>
    <button onclick="onClickLoop()">시작하기</button>
  </body>
</html>

실행순서가 아래와 같이 나온다.

실행 순서를 판단하는건 복잡한기능을 구현할 때 꼭 필요하다.

await와 마이크로큐의 관계

실행순서에 대해 더 알아보자.

퀴즈를 풀어보면서 순서를 예측해보았다.
그런데 철수가 왜 마지막에 나오는걸까?
이걸 이해해야 자바스크립트를 잘 할 수 있다.

\
bbb는 async가 앞에 붙어있다.
await는 프로미스이고 이건 기다려야되는구나라고 생각해 마이크로큐로 들어간다.
그러니 friend는 마이크로 큐에 들어갔고 bbb가 끝났다.

그리고 aaa-끝 을 찍고 마지막 끝을 한 다음에 마이크로큐에있는 철수를 담은 변수가 실행된다.
그러니 순서는 콜스택 -> 마이크로큐 -> 매크로큐 순으로 진행된다.

<!DOCTYPE html>
<html lang="ko">
  <head>
    <title>이벤트루프</title>
    <script>
      function onClickLoop() {
        console.log("=======시작!!!!=======");

        function aaa() {
          console.log("aaa-시작");
          bbb();
          console.log("aaa-끝");
        }

        async function bbb() {
          console.log("bbb-시작");
          await ccc(); // 여기가 포인트!! => await와 ccc()와는 관련이 없음
          console.log("bbb-끝");
        }

        async function ccc() {
          console.log("ccc-시작");
          const friend = await "철수";
          console.log(friend);
        }

        aaa();

        console.log("=======끝!!!!=======");
      }
    </script>
  </head>
  <body>
    <button onclick="onClickLoop()">시작하기</button>
  </body>
</html>

여기서 알 수 있는 사실은 await를 만나면 마이크로 큐로 들어간다는 걸 알 수 있다! 그리고 함수가 끝나버린다!
그 다음 async가 감싸는 함수도 마이크로 큐로 들어간다.
즉 await 뒤에 붙은 프로미스가 먼저 마이크로 큐에 들어가있고 다음에 async로 감싼 함수도 마이크로 큐 뒤에 가서 붙는다. 그래서 프로미스가 먼저 실행되고 나중에 함수가 실행되서 마치 기다리고 있는것처럼 보인다.
(함수나 변수 앞에 붙은 await는 상관이 없다!)

profile
총총

0개의 댓글