비동기로 바꿔보자 XMLHttpRequest 에서 Promise 함수를 이용, 그리고 마지막에는 async-awiat 를 사용하는 비동기 함수를 사용해서 단계별로 비동기 함수로 바꿔 나가는 과정을 기록하고자 한다. 전체 코드보다 api.ts 파일 부분을 주로 기록했다.
비동기 코드로 가져올 수 있도록 코드를 바꿔주기 위해 이벤트 핸들러를 사용해보자. 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 문제인것 같은데 강사님 코드와 비교해봐도 해결실마리를 찾지 못했다 ㅜㅜ
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("데이터를 불러오지 못했습니다.");
});
}
비동기코드를 어떻게 하면 더 효율적으로 사용 할 수 있을까? 에서 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 함수도 잘 몰랐었는데 비동기 함수와 함께 나오는 개념이라는 것을 알게 되었다.중간에 오류가 날때도 있었지만 천천히 보다보면 결국 오타 문제가 많았다. ^^;;... 오타를 내지 않도록 조심해야겠다. 허탈..
꽤 긴 여정이었다. 역시 들을때랑 정리하면서 들을 때랑 집중도가 다르고, 모르는 내용은 두세번 반복하고 이론부분도 보충하니 훨씬 이해도가 높아졌다.
해커뉴스 클론코딩을 하면서 코드가 무수히 바뀔 수 있다는 것과 어떻게 더 효율적으로 쓰는지 등을 좀 익히게 되었다. 이러한 사이클을 기억하면서 더 연습해봐야겠다.