TIL#24

박감자·2024년 10월 22일

TIL

목록 보기
24/34
post-thumbnail

달리기반 (4회차 실습)

1. 동기/비동기

setTimeout()을 이용한 동기/비동기 로직 실습!

기명함수를 사용한 방법은 따로 배운건 아니지만 setTimeouteventListener처럼 함수를 참조하는 값을 매개변수로 갖는다면 결과에 큰 차이가 없을거라고 생각했고, 다행히도(?) 잘 작동해서 1번의 경우처럼 사용해도 무방하다.

근데 매번 일일이 함수를 만드는 것도....너무 귀찮다ㅠㅠ
(모듈화 하려면 다 함수로 하는게 좋은건가...?)

console.log("Task 1 시작");
// 기명함수를 이용한 방법
function task1() {
  console.log("Task 1 완료");
}
setTimeout(task1, 3000);	// 3초 동안 기다리기

console.log("Task 2 시작");
// 2초 동안 기다리기
setTimeout(() => {
  console.log("Task 2 완료");
}, 2000);

추가적으로 delay가 0이어도 비동기 함수는 동기적으로 작동하는 부분 다음으로 실행되는 것이 보장(?) 되어있다. 이를 활용하여, 무조건 마지막에 실행되는 것을 이용하면 마지막에 실행했으면 하는 함수 혹은 로직을 setTimeout(func, 0); 로 지정하면 순서를 보장할 수 있다.

// 많은 코드 후에...

setTimeout(() => {
	console.log("This should be the last sentence");	// 항상 마지막에 나올 것임
}, 0);

2. Promise 실습

Promise를 활용한 비동기 작업을 만들어보는 것이 다음 실습

function orderCoffee(order) {
  return new Promise((resolve, reject) => {
    console.log(`${order} 커피 주문 중...`);
    setTimeout(() => {
      if (order === "에스프레소") {
        resolve(`${order}: 커피 준비 완료`);
      } else if (order === "아메리카노") {
        reject(`${order}: 이탈리아 출입 금지!!`);
      } else {
        reject(`${order}: 금일 재료가 소진 되었습니다`);
      }
    }, 2000);
  });
}

orderCoffee("에스프레소")
  .then((result) => console.log(result))
  .catch((error) => console.error(error));

orderCoffee("아메리카노")
  .then((result) => console.log(result))
  .catch((error) => console.error(error));

orderCoffee("프라푸치노")
  .then((result) => console.log(result))
  .catch((error) => console.error(error));

하면서 알게되었지만 경우에 따라 resolvereject를 정하는 것이 가능하다는 것 (한번씩만 지정할 수 있는지 알았다). 그래서 위와 같이 에스프레소 미만잡은 취급하지 않는 커피숍을 만들 수 있었다.

resolve의 경우는 .then()으로 잡아서 다음 작업이 가능하도록 하며, reject의 경우는 에러 캐치 하는 것처럼 .catch()를 통해 에러처리하면 된다.

3. async/await 사용하기

Promise.then().catch()보다는 사실 async/await가 좀 더 가독성이 좋고, 사용해보니 이게 더 활용하기 더 좋은 편인것 같다.

async function makeCoffee() {
  try {
    const result = await orderCoffee("아메리카노");
    console.log(result);
  } catch (error) {
    console.error(error);
  }
}
    
makeCoffee();

4. Promis.all

마지막으로 비동기적 작업을 한번에 받고 다음 작업으로 넘어가고 싶을 때 Promise.all을 사용하면 편리하다

async function orderPizzas() {
  try {
    const orders = await Promise.all([
      preparePizza("페퍼로니"),
      preparePizza("하와이안"),
      preparePizza("치즈피자"),
      preparePizza("마르게리타"),
      preparePizza("고르곤졸라")
    ]);

    orders.forEach(order => {
      console.log(`${order}`);
    });
  } catch (error) {
    console.log(error);
  }
}

orderPizzas();

배열의 형태로 돌려주기 때문에 구조분해할당을 해도 좋으며 위처럼 forEach도 사용이 가능하다.

5. 네트워크 요청 실습

fetch를 사용하다보면 await를 두 번 사용해야하는데 대부분 아래처럼 사용하게 된다.

async function fetchData() {
    try {
        const res = await fetch("...");
        if (!res.ok) {
            throw new Error("네트워크 응답 오류");
        }

        // fetch의 경우 일단 header를 먼저 준다고 한다
        // body를 주기 전!!
        // 따라서 body까지 받기 위해서 한 번 더 기다려야 한다.
        const data = await res.json();

        console.log(data);
    } catch (error) {
        console.log("에러 발생:", error);
    }
}

fetchData();

주석에도 써놓았지만 fetch가 작동하는 방식이 헤더 부분을 먼저 주고 거기서 한 번 더 await.json()을 해주어야 다음 바디 부분을 준다. 이때 .json()을 해주는 이유는 먼저 반환된 헤더 부분이 Promise로 반환되기 때문이다. 이를 읽으려면 .json() 부분이 필요한 것!

개인프로젝트

디바운싱 (debouncing) / 쓰로틀링 (Throttling)

API 요청이 많아지면 렉이 심해지는 현상이 있는데, 특히 구현한 검색기능이 조금씩 버벅일때가 많았다. 실시간 검색기능을 구현하고자 문자 하나하나 칠때마다 API 요청을 불러오는 식으로 코드를 짠 탓이 크다. 이를 방지해주는 것이 Debouncing과 Throttling이다.

결론만 놓고 말하자면

  • Debouncing : 연속된 같은 요청에서 마지막 요청으로부터 일정한 시간이 지났을때 마지막 요청만 전송하는 것
  • Throttling : 일정한 시간동안 첫번째 요청으로부터 요청의 횟수를 제한하는 것


위 자료처럼 'Canada'라는 항목을 실시간 검색하는 로직에서 볼때, 문자마다 입력값을 받는 것이 normal 케이스이다. 매 문자마다 api 요청을 보낸다고 하면은 'C', 'Ca', 'Can', ... 이런식으로 비효율적인 요청을 보내고 있는 것이다.

그래서 요청 횟수를 제한하거나 마지막 요청만 보내는 방식의 코드가 유용한 것

Throttling

우선 개념과 참고한 자료들을 통해 짠 Throttle 코드는 아래와 같다

// ui.js에 구현
// Trottling 함수
export function throttle(func, delay) {
  let timer = null;

  return function () {
    const args = arguments;
    if (!timer) {
      func.apply(this, args);
      // 타이머 값 지정
      timer = true;
      timer = setTimeout(() => {
        timer = null;
      }, delay);
    }
  };
}

// 아래는 script.js에서 콜할때
searchInput.addEventListener("input", throttle(searchMovie, 500));

타이머라는 변수를 두어 타이머의 값이 false 혹은 값이 없을때만 새로운 타이머를 세팅하는 방식이다. 그러면 다음 요청때 타이머 값이 참이면 앞의 요청이 간지 얼마 안된것이기 때문에 따로 해주는 것이 없고, 거짓일때는 첫 요청이 간지 시간이 조금 된것이기 때문에 다시 한 번 요청을 보내게끔 되는 것.

사실 ChatGPT가 보여준 코드가 더 이해하기가 쉬운데

export function throttle(func, delay) {
  let lastCall = 0; // Keeps track of the last time the function was called

  return function () {
    const now = Date.now(); // Get the current timestamp
    const args = arguments;

    if (now - lastCall >= delay) {
      // If enough time has passed since the last call, execute the function
      lastCall = now;
      func.apply(this, args);
    }
  };
}

타이머가 아닌 시간을 체크해서 많이 지난 것 같네 하면 요청을 다시 보내는 로직.

단점이 있다면 위에 Canada 이미지에서도 보이겠지만 마지막 요청을 파악하지 못한다. 'Canada'라고 요청이 가야하지만 'Cana'에서 요청이 한 번 가고 문자를 더 이상 입력하지 않으면 타이머가 지났는지 체크가 되지 않기 때문에 'Cana'만 검색한 꼴이 되어버린다. 내 경우도 그러했다 (아래는 참고 사진)

어밴져를 검색했지만 복수하다의 avenge만 요청으로 보내진 모습.

추가 TMI)
사실 요청을 시간을 줄이면 일시적으로 해결할 수는 있으나, 결국 매문자마다 요청을 보내던 처음 코드와 같아지므로 근본적인 해결책은 아니다.

searchInput.addEventListener("input", throttle(searchMovie, 500));
searchInput.addEventListener("input", throttle(searchMovie, 300));	// 제한시간이 짧아서 전체적으로 요청을 더 많이 보냄

Debouncing

위와 같은 상황을 방지하는 것은 Debouncing이다. 마지막 요청만은 확실히 보내기 때문! 아래가 예시 코드

// ui.js
// Debouncing 함수
export function debounce(func, delay) {
  let timer;
  return function () {
    const args = arguments;
    clearTimeout(timer);
    timer = setTimeout(() => {
      func.apply(this, args);
    }, delay);
  };
}

// script.js
searchInput.addEventListener("input", debounce(searchMovie, 500));

매번 새로운 입력값이 들어올때마다 타이머를 새로 설정한다. 그래서 마지막 요청으로부터 새로운 입력값 없이 일정한 시간이 지난다면 그제서야 실행

마지막 Avenger만 깔끔히 보낸 모습. 단점이 있다면 실시간 검색이 되지는 않는다, 유저가 검색어를 다 입력할때까지 기다리기 때문

참고한 자료들
https://onlydev.tistory.com/151
https://cheolsker.tistory.com/70

마치며...

debouncing은 이해도 구현도 '나쁘지 않군' 이러면서 했는데 throttle이.... 영어 뜻 그대로 '목을 조르다' 같은 느낌으로 뇌가 답답한 느낌으로 이해하고 구현은 결국 인터넷 선생님들의 도움이 간절했다.

Throttling은 사실 입력값을 받을때보다 요청을 보내야할때로 지정해주면 효과를 발한다고 합니다.

profile
코딩하는 감자

1개의 댓글

comment-user-thumbnail
2024년 10월 22일

이야아 참 잘했어요! 1따봉 들임 ^-^b

답글 달기