혼자 적어보는 노트

프로그래머스 자바스크립트 스터디 - mission3 본문

스터디

프로그래머스 자바스크립트 스터디 - mission3

jinist 2022. 2. 8. 09:42

3주차는 API관련해서 요청을 보내고 요청 받은 데이터를 가지고

화면에 노출하는 미션이었다.

 

mission3에서 다룰 주제는 비동기 제어, callback, promise, aync~await 이다.

 


 

1. fetch

fetch를 이용하여 api를 요청하고 then으로 호출하던 부분을 async await로 변경하였다.

const getSearchData = async (keyword, fromHistory) => {
  try {
    if (keyword.length < 1) {
      searchResult.setState([])
      return
    }
    const response = await fetch(
      `https://jjalbot.com/api/jjals?text=${keyword}`
    )
    const data = await response.json()
    searchResult.setState(data)
    !fromHistory && searchHistory.addHistory(keyword)
  } catch (error) {
    console.log(error)
  }

위와 같이 데이터를 요청하는 코드를 작성했는데

fetch의 기본 스펙중에 fetch()로부터 반환되는 promise 객체는 HTTP error상태를 reject하지 않는다고 한다.

response의 내용을 보면 ok 상태를 확인할 수 있는데 이 부분으로 요청에 대한 응답을 받을 수 있다.

 

✅ 에러 코드 추가

if (!response.ok) {
      throw new error('API 요청에 실패하였습니다.')
}

 

 

API코드 분리하기.

상위 컴포넌트에 API호출과 함께 관련된 코드들을 한 함수에 넣어주었었다.

분리를 하고 싶다는 생각은 했으나 어떤 식으로 분리를 해야 될 지 고민을 하다 패스 했었다 (반성해야함..!😓)

 

const getSearchData = async (keyword, fromHistory) => {
  try {
    if (keyword.length < 1) {
      searchResult.setState([])
      return
    }
    const response = await fetch(
      `https://jjalbot.com/api/jjals?text=${keyword}`
    )
    const data = await response.json()
    searchResult.setState(data)
    !fromHistory && searchHistory.addHistory(keyword)
    if (!response.ok) {
      throw new error('API 요청에 실패하였습니다.')
    }
  } catch (error) {
    console.log(error)
  }
}

위처럼 이렇게 합쳐서 관리하게 되면 이후 반복적인 api호출이 필요할 경우

다시 작성 해야 한다.

 

✅ 수정한 코드

 

[분리한 api.js]

const API_URL = 'https://jjalbot.com'

const request = async (url) => {
  try {
    const response = await fetch(`${API_URL}${url}`)
    if (!response.ok) {
      throw new Error('api error')
    }
    return await response.json()
  } catch (error) {
    console.log(error)
  }
}

export const fetchJjalImages = (keyword) =>
  request(`/api/jjals?text=${keyword}`)

API 호출을 외부 함수로 분리하고 이후에 반복될 수 있는 부분을 고려하여 작성한다.

예제는 keyword를 받아서 하는 api 1개지만 위처럼 url을 상수로 입력해두고 사용할 경우

여러개의 api의 관리 측면에서도 용이해진다는 것을 알게되었다.

 

[수정된 getSearchData 함수]

const getSearchData = async (keyword, fromHistory) => {
  try {
    if (keyword.length < 1) {
      searchResult.setState([])
      return
    }
    const data = await fetchJjalImages(keyword)
    searchResult.setState(data)
    !fromHistory && searchHistory.addHistory(keyword)
  } catch (error) {
    console.log(error.message)
  }
}

 

전보다는 코드 길이만 보면 조금 길겠지만 무언가 각각 나누어서 관리를 하게되니

나중에 코드를 봐도 이해하기가 쉬울 듯하다.

 

 


2. debounce

키보드 입력에 이벤트를 걸고 검색결과가 출력되게 했는데

api를 호출하여 일일히 검색결과를 호출할 경우 불필요한 api호출을 하게 된다.

debounce기법을 사용하여 빠르게 키보드를 입력할 경우 마지막으로 입력된 키워드를 사용하여

api호출을 줄여주는 방법을 알게 되었다.

 

export default function SearchInput(elementId, getSearchData) {
  const $searchInput = document.querySelector(elementId)
  let inputTimer

  $searchInput.addEventListener('keyup', function (e) {
    if (inputTimer) {
      clearTimeout(inputTimer)
    }
    inputTimer = setTimeout(function () {
      getSearchData(e.target.value.trim())
    }, 300)
  })

input의 keyup이벤트에 이벤트를 걸어주었고

setTimeout와 clearTimeout을 이용하여 컨트롤을 해주었다.

궁금했었던 기능이였지만 직접 해 볼 일이 없었는데 이번에 직접 해보면서 알게 되었다.

 

💥 debounce 로직을 util로 분리해보라는 피드백을 받았다.

 

어떻게 분리를 하면 좋을 지 애매해서 어려웠다 ㅜ inputTimer에 데이터가 있는지에 따라

setTimeout을 컨트롤 해야 하는데 콜백함수만 따로 빼면 inputTimer의 값을 참조 할 수 없는 등

debounce의 기능 자체를 깔끔하게 처리할 로직이 잘 떠오르지 않았다.

 

 

✅ 수정한 코드

 

[searchInput]

  $searchInput.addEventListener(
    'keyup',
    debounce((e) => {
      getSearchData(e.target.value.trim())
    }, 300)
  )

 

[util.js]

export const debounce = (callback, delay) => {
  let timer

  return (event) => {
    if (timer) {
      clearTimeout(timer)
    }
    timer = setTimeout(callback, delay, event)
  }
}

debounce함수를 만들고 콜백으로 받을 함수와 딜레이할 값을 받고

함수를 리턴하여 받은 파라미터들을 활용하여 setTimeout을 실행하였다.

 

+ 미션 이후 세션에서 알려주신 코드와는 차이가 있었지만 이렇게 코드를 짤경우

어떤 방식으로 함수가 실행이 되는지 한번 더 생각할 수 있는 기회가 되었다.

 

 


 

3. addEventListener()의 익명함수

스터디에서 나온 내용은 아니지만 질문을 통해 받은 내용이다.

반복적인 호출이 일어나는 함수 내부에서 addEventListener()의 콜백 함수에 익명 함수를 사용할 경우
함수 내용이 동일함에도 불구하고 항상 다른 함수로 인식하기 때문에 한 대상의 이벤트 수신기로 부착이 된다.

 

[해당코드]

  this.render = () => {
    this.$history.innerHTML = `
    ${this.historyData
      .map((history) => `<li class="history-item"> ${history} </li>`)
      .join('')}
    `

    this.$history.addEventListener('click', (event) => {
      console.log('click 이벤트 발생')
      if (event.target.className === 'history-item') {
      getSearchData(event.target.innerText, true)
      }
    })
  }

 

render함수 내부에서 addEventListener()를 생성하고 콜백함수에 익명함수를 넣어주었는데

클릭을 1번 했음에도 불구하고 render가 호출된 만큼 클릭이벤트가 중첩되어 실행이 된다.

 

✅ 해결방법 1

  const clickHistory = (event) => {
    console.log('click 이벤트 발생')
    if (event.target.className === 'history-item') {
      getSearchData(event.target.innerText, true)
    }
  }
  this.render = () => {
    this.$history.innerHTML = `
    ${this.historyData
      .map((history) => `<li class="history-item"> ${history} </li>`)
      .join('')}
    `

    this.$history.addEventListener('click', clickHistory)
  }

위와 같이 콜백함수를 따로 상단에 만들어주면 해결을 했었는데

익명함수를 사용하면서 해결하는 방법이 또 있었다.

 

✅ 해결방법 2.

  this.$history.addEventListener('click', (event) => {
    console.log('click 이벤트 발생')
    if (event.target.className === 'history-item') {
      getSearchData(event.target.innerText, true)
    }
  })
  
  this.render = () => {
    this.$history.innerHTML = `
    ${this.historyData
      .map((history) => `<li class="history-item"> ${history} </li>`)
      .join('')}
    `
  }

익명 함수를 사용하고 싶다면

위 코드와 같이 반복적으로 호출되는 render함수의 바깥에 addEventListener()를 사용해주면 된다.

 

 

스터디를 진행하기 한참 이전에 반복문 내부의 addEventListener()에

익명함수를 함께 사용해서 이와 같은 현상을 겪은 적이 있었는데

해결을 하기 위해 콜백함수를 바깥으로 뺀 기억이 있지만 원리를 몰랐어서 의아함으로 남았었는데

답변을 주셔서 해결을 하였다. 🙇‍♀️

 

공식 문서에 다 있었다!! 공식문서를 잘 읽어보자!!

 

 


 

✍ mission 3을 진행하면서 느낀 점

 

promise에 대한 이론적인 것은 봐왔어서 코드를 보고 이해는 가능했으나
실습을 통한 이해가 적었었고. API를 다루는 것 또한 몇번 해보기는 했지만 완전히 몸에 익은 것은
아니였기 때문에 하면서 살짝 버벅이는 부분도 있었다.

특히나 콜백으로 이뤄진 코드를 promise로 변경하는 것에 대해 이해가 부족함을 알게되었다.

그래도 처음에 컴포넌트 만들 때 비해서는 수월하게 진행 했던 것 같다!👍
그리고 코드를 분리해서 작성해야되지 않을까? 라는 생각이 들면

보통 코드를 분리하는게 맞는 것 같다..🤣 생각만 하지말고 분리 해보며 습관을 길러야겠다!

 

그리고 스터디를 하면서 mission마다 각 브랜치를 생성해서 pull request(PR)을 보내는데

혼자 할 때는 몰랐지만 git에 대해 이해가 부족한 상태에서 다른 사람들과 함께 저장소를 사용하다보니

충돌이나 실수한 상황에 대처하기가 어려웠다.ㅜ

답답한 마음에 git을 처음부터 다시 학습하기로 했다..

혼자만 했으면 몰랐을 부분인데 중요성을 알게되어 하게 된 것이라 좋은 경험인 것 같다.

Comments