비동기로 전환하기

dev__bokyoung·2022년 9월 9일
0

해커뉴스 클론코딩

목록 보기
12/12
post-thumbnail

프롤로그

비동기로 바꿔보자 XMLHttpRequest 에서 Promise 함수를 이용, 그리고 마지막에는 async-awiat 를 사용하는 비동기 함수를 사용해서 단계별로 비동기 함수로 바꿔 나가는 과정을 기록하고자 한다. 전체 코드보다 api.ts 파일 부분을 주로 기록했다.

1. 비동기 옵션 - 이벤트 핸들러 'load'

비동기 코드로 가져올 수 있도록 코드를 바꿔주기 위해 이벤트 핸들러를 사용해보자. return 값을 받아 줄 수 있는 곳이 없으므로 callback 함수를 써서 받아줘야 한다.

export class Api {
  ajax: XMLHttpRequest;
  url: string;

  constructor(url: string) {
    this.ajax = new XMLHttpRequest();
    this.url = url;
  }

  getRequest<AjaxResponse>(cb: (data: AjaxResponse) => void): void {
    this.ajax.open("GET", this.url);
    // 이벤트 리스너를 등록
    this.ajax.addEventListener("load", () => {
      cb(JSON.parse(this.ajax.response) as AjaxResponse);
    });

    this.ajax.send(); //데이터를 가져옴
  }
}

//cb 함수를 받아 넘겨줘야 한다. 실제로 사용하는 부분은 view 쪽
export class NewsFeedApi extends Api {
  constructor(url: string) {
    super(url);
  }
  getData(cb: (data: NewsFeed[]) => void): void {
    return this.getRequest<NewsFeed[]>(cb);
  }
}

export class NewsDetailApi extends Api {
  constructor(url: string) {
    super(url);
  }

  getData(cb: (data: NewsDetail) => void): void {
    return this.getRequest<NewsDetail>(cb);
  }
}

이제 실제 사용하는 view 부분
newsdetail


render = (id: string): void => {
    const api = new NewsDetailApi(CONTENT_URL.replace("@id", id));

    // const newsDetail: NewsDetail = api.getData();
    //return 으로 인자를 받을 수 없으니 인자에게 함수를 제공한다.
    api.getData((data: NewsDetail) => {
      const { title, content, comments } = data;

      this.store.makeRead(Number(id));
      this.setTemplateData("comments", this.makeComment(comments));
      this.setTemplateData("currentpage", String(this.store.currentPage));
      this.setTemplateData("title", String(title));
      this.setTemplateData("content", String(content));

      this.updateView();
    });
  };

newsfeed
detail 부분과 다르게 생성자에서 api 호출하고 있다. render 라고 하는 함수는 라우터가 호출하므로 데이터가 왔을지 안왔을지 보장이 안됨! 그래서 생성자에서 api 호출하는 부분들을 render 로 옮겨와줘야 한다.


 render = (page: string = '1'): void => {
    this.store.currentPage = Number(page);

    if (!this.store.hasFeeds) {
      this.api.getDataWithPromise((feeds: NewsFeed[]) => {
        this.store.setFeeds(feeds);
        this.renderView();
      });
    }

    this.renderView();
  }

// 따로 분리 해 주었다.
  renderView = () => {
    for(let i = (this.store.currentPage - 1) * 10; i < this.store.currentPage * 10; i++) {
      const { id, title, comments_count, user, points, time_ago, read } = this.store.getFeed(i);

      this.addHtml(`
        <div class="p-6 ${read ? 'bg-red-500' : 'bg-white'} mt-6 rounded-lg shadow-md transition-colors duration-500 hover:bg-green-100">
          <div class="flex">
            <div class="flex-auto">
              <a href="#/show/${id}">${title}</a>  
            </div>
            <div class="text-center text-sm">
              <div class="w-10 text-white bg-green-300 rounded-lg px-0 py-2">${comments_count}</div>
            </div>
          </div>
          <div class="flex mt-3">
            <div class="grid grid-cols-3 text-sm text-gray-500">
              <div><i class="fas fa-user mr-1"></i>${user}</div>
              <div><i class="fas fa-heart mr-1"></i>${points}</div>
              <div><i class="far fa-clock mr-1"></i>${time_ago}</div>
            </div>  
          </div>
        </div>    
      `);
    }  

    this.setTemplateData('news_feed', this.getHtml());
    this.setTemplateData('prev_page', String(this.store.prevPage));
    this.setTemplateData('next_page', String(this.store.nextPage));
  
    this.updateView();
  }

에러 봉착!

왜 id 값을 못 가져오지??
api 문제인것 같은데 강사님 코드와 비교해봐도 해결실마리를 찾지 못했다 ㅜㅜ

2. promise 버전으로 변경

XMLHttpRequest 를 promise 버전으로 바꾸어 보았다. fetch 함수도 사용했는데 차세대 버전이라고 한다. (둘 차이를 비교!)

getRequestWithXHR<AjaxResponse>(cb: (data: AjaxResponse) => void): void {
    this.xhr.open("GET", this.url);
    // 이벤트 리스너를 등록
    this.xhr.addEventListener("load", () => {
      cb(JSON.parse(this.xhr.response) as AjaxResponse);
    });

    this.xhr.send(); //데이터를 가져옴
  }

  // callback 지옥을 막기 위해 나온 promise
  getRequestWithPromise<AjaxResponse>(cb: (data: AjaxResponse) => void): void {
    // 새로운 api, 차세대 api - xhr 을 대응하는 새로운 api. promise 베이스의 api
    //xhr 의 json.parse 는 동기적으로 작동 - 데이터가 커지면 ui 가 멈추게 된다.
    //반면 fetch 는 json 자체를 비동기적으로 객체로 바꾸는 기능을 제공함으로써 해결을 하고 있음
    fetch(this.url)
      .then((response) => response.json())
      .then(cb)
      .catch(() => {
        console.error("데이터를 불러오지 못했습니다.");
      });
  }

3. 콜백 함수 없는 비동기 코드 작성법

비동기코드를 어떻게 하면 더 효율적으로 사용 할 수 있을까? 에서 promise 메커니즘을 생각 해 내었고, 이제 더 나아가 async,awiat 를 사용하는 비동기 함수를 이용해서 내부적인 메커니즘은 콜백처럼 작동을 하는 비동기 코드임에도 불구하고, 코드 상으로는 완전한 동기체계로써 보이게 작성을 할 수 있는 문법 체계 로 발전되었다고 한다. 비동기 함수 또한 Promise 메커니즘을 바탕으로 작성된 코드여서 Promise 메커니즘에 대한 공부가 필요하다.

import { NEWS_URL, CONTENT_URL } from "../config";
import { NewsFeed, NewsDetail } from "../types";

export class Api {
  xhr: XMLHttpRequest;
  url: string;

  constructor(url: string) {
    this.xhr = new XMLHttpRequest();
    this.url = url;
  }

  async request<AjaxResponse>(): Promise<AjaxResponse> {
    const response = await fetch(this.url);
    return (await response.json()) as AjaxResponse;
  }
}


export class NewsFeedApi extends Api {
  constructor() {
    super(NEWS_URL);
  }

  async getData(): Promise<NewsFeed[]> {
    return this.request<NewsFeed[]>();
  }
}

export class NewsDetailApi extends Api {
  constructor(id: string) {
    super(CONTENT_URL.replace("@id", id));
  }
  async getData(): Promise<NewsDetail> {
    return this.request<NewsDetail>();
  }
}

결과 화면

에필로그

비동기 함수 부분은 조금 버거웠다. 그래서 Promise 메커니즘, 비동기 함수 부분의 이론을 따로 찾아봤더니 조-금은 이해가 되었다. async-await 패턴도 결국 Promise 메커니즘 기반이라고 하니 잘 익혀 둬야 겠다.
callback 함수도 잘 몰랐었는데 비동기 함수와 함께 나오는 개념이라는 것을 알게 되었다.중간에 오류가 날때도 있었지만 천천히 보다보면 결국 오타 문제가 많았다. ^^;;... 오타를 내지 않도록 조심해야겠다. 허탈..
꽤 긴 여정이었다. 역시 들을때랑 정리하면서 들을 때랑 집중도가 다르고, 모르는 내용은 두세번 반복하고 이론부분도 보충하니 훨씬 이해도가 높아졌다.
해커뉴스 클론코딩을 하면서 코드가 무수히 바뀔 수 있다는 것과 어떻게 더 효율적으로 쓰는지 등을 좀 익히게 되었다. 이러한 사이클을 기억하면서 더 연습해봐야겠다.

profile
개발하며 얻은 인사이트들을 공유합니다.

0개의 댓글