본문 바로가기
  • soldonii's devlog
Javascript 공부/TIL

191120(수) : response 순서대로 가져오기 등

by soldonii 2019. 11. 20.

1. Response 순서대로 가져오기

바닐라코딩 어드미션 테스트 합격 후 추가과제로 받은 Hacker News API를 이용하여 Hacker News website 카피하기 과제를 며칠 붙잡고 헤맸다. 다른 부분은 큰 문제는 없었는데, 제이쿼리를 이용해 비동기로 데이터를 요청한 후, 요청한 순서대로 데이터를 받아서 DOM에 추가해야 했다.

 

우선 전체 story의 id가 담긴 정보를 받는 것은 단순한 요청이라 어렵지 않았다.

$.get('https://hacker-news.firebaseio.com/v0/topstories.json?print=pretty', (storyArr) => {
  let top30 = storyArr.slice(0, 30);
}

상위 30개 article만 먼저 보여주면 되기 때문에 slice로 상위 30개만 잘랐다. 이 요청은 무척 간단했다. 그런데 문제는 top30에 담긴 정보는 article 정보를 담은 id이기 때문에, 세부 article 내용을 알려면 id를 전달해서 다시 정보를 요청하고 응답받아야 했다.

 

# 첫번째 문제

처음에는 for문을 이용해서 정보를 요청하고 전달받으려고 했다.

$.get('https://hacker-news.firebaseio.com/v0/topstories.json?print=pretty', (storyArr) => {
  let top30 = storyArr.slice(0, 30);

  let articlesArr = [];
  for (let i = 0; i < top30.length; i++) {
    $.get(`https://hacker-news.firebaseio.com/v0/item/${top30[i]}.json?print=pretty`, resp => articlesArr.push(resp));
  }
  // 여기서 작업하기
});

그런데 우선 문제는 여기서 작업하기라고 써있는 부분에서, 즉 나는 응답받은 데이터를 articlesArr 배열에 담아서 얘를 다루려고 했는데, 비동기적인 요청의 특성 상 그렇게 하면 정보를 이용할 수가 없었다.

 

왜냐하면 비동기라는 것은 정보를 요청하고, 응답받는 것을 기다리는 동안 다음 코드를 실행하게 되는데, '여기서 작업하기' 부분에 내가 코드를 쓰면, 실제로 코드 실행이 되는 순간은 서버로부터 응답을 받기 이전이기 때문에 빈 배열에 불과하므로 정보를 다룰 수가 없는 것이다. 진짜 이거 어떻게 해보겠다고 거의 이틀 내내 googling하고, 여기저기 질문도 올려보고 했는데 결국 해결 못해서 ken 님께 slack으로 문의드렸다. 결론은 외부에서는 어떻게 해도 그 정보를 이용할 수 없다는 것이었다.

 

# 두번째 문제

첫번째 문제는 불가능하다는 것을 모른 채 이틀이나 시간을 낭비해서 속상했지만, 해결이 어렵지는 않았다. 단순히 $.get 코드 안에서 작업을 하기만 하면 됐으니까. 그런데 진짜 문제는 두번째 문제였다.

top30 배열에는 상위 30개의 id 정보가 순차적으로 들어있는데, 얘를 나는 for 문을 이용해서 순차적으로 서버에 세부정보를 요청하고 받아서 작업하려 했는데, 내가 순차적으로 요청해도 작업은 서버로부터 응답받은 순서대로 하는 문제가 발생했다.

 

그니까 나는 1, 2, 3, 4, 5, ... 의 순서대로 요청했는데, 응답받은 순서는 3, 5, 1, 2, 4, .. 이런식으로 무작위로 그 때 그 때 달랐다. 따라서 실제 Hacker News 웹사이트에 표시된 기사 순서대로 DOM에 그려지지 않고 random하게 응답받은 순서대로 그려졌던 것이다. 진짜 이것도 한 이틀은 헤맸다. 그러다가 오늘 어떻게 뭔가 기적적으로 해결했는데, 잊지 않기 위해서 아래 코드를 정리한다.

 

맨 처음 TIL에서도 썼던 건데, 나는 시작에 무식한 방법(brutal force)으로 접근하지 않고, 처음부터 최대한 깔끔하고 완벽한 코드를 쓰려는 습관이 있다. 오늘 그걸 깨서 그냥 내가 머리속에서 생각한 대로 진짜 무식하게 코드를 작성해봤다. 그랬더니 아래처럼 하면 될 것 같았다.

$.get('https://hacker-news.firebaseio.com/v0/topstories.json?print=pretty', (storyArr) => {
  let top30 = storyArr.slice(0, 30);

  let articlesArr = [];
  $.get(`https://hacker-news.firebaseio.com/v0/item/${top30[0]}.json?print=pretty`, resp0 => {
    articlesArr.push(resp0);

    $.get(`https://hacker-news.firebaseio.com/v0/item/${top30[1]}.json?print=pretty`, resp1 => {
      articlesArr.push(resp1);

      $.get(`https://hacker-news.firebaseio.com/v0/item/${top30[2]}.json?print=pretty`, resp2 => {
        articlesArr.push(resp2);

        $.get(`https://hacker-news.firebaseio.com/v0/item/${top30[3]}.json?print=pretty`, resp3 => {
          articlesArr.push(resp3);
          
          // 30번 될때까지 계속...
        });
      });
    });
  });
});

소위 말하는 콜백지옥(?)과 같은 형태인데, 이렇게 작성해보니까 '아! 뭔가 재귀함수로 해결할 수 있겠다!'라는 생각이 갑자기 들었다. 재귀함수로 30번 반복한 후에, 가장 깊게 들어간 30번째 실행 컨텍스트에서 articlesArr에 30개의 세부 정보가 담겨있으므로, 그 곳에서 작업을 하면 되겠다는 생각이 든 것이다!

 

그래서 결국 해결한 코드는...

$.get('https://hacker-news.firebaseio.com/v0/topstories.json?print=pretty', (storyArr) => {
  let top30 = storyArr.slice(0, 30);
  console.log(top30);
  multipleReq(top30, counter);
});

let counter = 0;
function multipleReq(target, n, resultArr = []) {
  if (resultArr.length === 30) return paintDOM(resultArr);

  $.get(`https://hacker-news.firebaseio.com/v0/item/${target[n]}.json?print=pretty`, (resp) => {
    resultArr.push(resp);
    return multipleReq(target, n+1, resultArr);
  });
}

우선 base case인 결과 배열의 길이가 30일 될 때는, DOM에 결과물들을 추가하는 paintDOM이라는 함수를 리턴시키도록 했다. 그리고 길이가 30이 안 될 경우에는, counter를 1씩 증가시키면서 요청 내에서 계속 추가적으로 요청을 해서 담았다. 이렇게 하면 하나의 요청이 완료되어서 배열에 담긴 후에, 배열의 길이를 판단하고, 배열의 길이가 30이 안되면 그 요청 안에서 또 새로운 요청을 하게 되므로 결과적으로 내가 요청한 순서대로 정보가 담기게 됐다.

 

진짜 카페에서 몇시간 또 오늘도 끙끙대다가 이거 해결했을 때 진심 희열이 느껴졌다. 사실 성능적인 측면에서는 완전 똥망일지 아닐지 잘은 모르겠는데.. 지금 내 수준에서는 원하는 결과를 얻은 것만으로도 감사하다..ㅠㅠ

 

물론 검색했을 때 나온 답변들인 Promise.all이나 Async Await 같은 개념을 활용하면 훨씬 쉽게(?) 해결할 수 있다는 것 같다. 그 사실은 알지만 이번 과제에서는 Promise를 사용하지 말고 하라는 내용이 있었기 때문에, 일부러 사용하지 않고 최대한 꾸역꾸역 해결한 방법이다.

 

# 결론

1. 응답받은 데이터를 외부에서 사용하는 것은 불가능하다.

2. 요청을 순서대로 받고자 할 경우, 요청 내에서 또 요청을 하는 방식으로(ex. 재귀함수) 해결할 수 있다.(?)

 

2. InsertAdjacentHTML

얘는 처음 써본거라서 정리한다.

Target Element의 HTML 내용을 변경할 때에는 innerHTML을 사용하지만, 내용 변경이 아니라 HTML 코드를 추가할 때에는 insertAdjacentHTML을 사용할 수 있다.

 

innerHTML MDN 문서

☞ insertAdjacentHTML MDN 문서

 

3. CSS Link 서식 관련

맨날 써도 맨날 헷갈려서 이번 기회에 정리!

a:link { color: blue; }          /* 방문하지 않은 링크 */
a:visited { color: purple; }     /* 방문한 링크 */
a:hover { background: yellow; }  /* 마우스를 올린 링크 */
a:active { color: red; }         /* 활성화한 링크 */

댓글