WeniVooks

검색

JavaScript 베이스캠프

fetch와 async, await

1. 비동기 통신

이번 장에서는 실무에서 많이 사용하는 fetch에 대해 알아보겠습니다. fetch는 여러분이 사용하는 웹 서비스에서 서버와 통신하기 위해서 사용합니다. 예를 들어, 숙박 웹이나 음식 주문 웹을 이용하면 기본 html 파일을 받아와서 여러분 화면에 기본 골격을 완성해 놓고 서버로부터 데이터를 하나씩 받아와서 기본 골격에 대입해가며 화면을 완성하죠. 이때 서버와 통신하기 위해 fetch를 사용합니다.

'이렇게 복잡한 절차를 왜 거치지? 서버에서 모든 HTML, CSS, JS를 다 주면 해결되는 일 아닐까?' 이런 의문이 있으실 수도 있습니다. 여러분이 생각하는 방식을 '모놀리식 아키텍처'라고 합니다. 모놀리식 아키텍처는 서버에서 모든 것을 처리하는 방식입니다. 이 방식은 서버에 부하가 많이 가고, 사용자가 많아지면 서버가 느려지는 단점이 있습니다. 그래서 현재는 많은 서비스들이 위와 같이 fetch를 이용하여 이미지는 이미지 서버에서, 데이터는 데이터 서버에서, 채팅은 채팅 서버에서 사용하는 '마이크로서비스 아키텍처'를 사용합니다. 이렇게 하면 서버의 부하를 분산시킬 수 있고, 사용자가 많아져도 서버가 느려지는 문제를 해결할 수 있습니다. 그렇다 하여 항상 마이크로서비스 아키텍처가 정답인 것은 아닙니다. 상황과 개발 규모에 따라 아키텍처 선택은 달라집니다. 이러한 아키텍처를 이해하고 선택하고 반영하는 것은 초급자의 범위를 넘어선 내용이니 참고만 해주세요. 핵심만 다시 요약해 드리자면, fetch를 사용하면 서버와 통신하여 데이터를 받아올 수 있습니다.

위니브에서 개발하고 있는 스터디인(https://www.studyin.co.kr/) 서비스에 접속하여 네트워크 탭을 클릭한 후 새로고침을 해보세요. 이렇게 하면 서버로부터 데이터를 받아오는 과정을 확인할 수 있습니다. 여기서 아래 이미지처럼 Fetch/XHR이라는 항목을 클릭해보세요. 실제 이 서비스가 어떠한 데이터를 받아와서 화면에 출력하는지 확인할 수 있습니다. 그리고 각각의 파일을 클릭한 다음 우클릭하시면 fetch 코드도 확인할 수 있습니다.

2. fetch

이처럼 fetch는 실무에서 매우 많이 사용되는 코드입니다. fetch는 자바스크립트에서 제공하는 비동기 통신 API입니다. 이를 사용하면 앞서 본 것 처럼 서버에 네트워크 요청을 보내고 새로운 정보를 받아올 수 있습니다. 실무에서 자주 사용되기 때문에 아래와 같은 구현 테스트 문제로도 자주 출제 됩니다. 구현 테스트는 보통 서류 전형을 통과하고 면접 전에 보는 테스트를 말합니다.

fetch는 앞서 배운 Promise를 기반으로 구현되어 있습니다. .then()이나 .catch()를 통해 코드 분기를 할 수 있는 것이죠. 응답 값에 따라 .then()이나 .catch()를 적절히 사용할 수 있습니다. 따라서 이번 장을 들어가기 전 Promise에 대한 이해가 필요합니다. 혹시 이 챕터로 바로 넘어왔다면 앞 챕터인 Promise를 먼저 읽어보세요.

예제 코드에서는 fetch()를 사용하여 https://test.api.weniv.co.kr/mall에 데이터 요청을 보내고, 응답 데이터를 받아와 다양한 방식으로 처리하는 과정을 보여줍니다.

이 코드를 단번에 이해하기는 힘들겠지만 이러한 과정을 거쳐 우리가 실제 사용하는 웹 쇼핑몰 서비스가 동작하게 됩니다. 아래 코드를 실행시켜 보세요. 만약 about:blank 콘솔 창에 붙여넣고 실행하실 것이라면 주석되어 있는 부분을 주석을 풀고 실행해보세요. console.log 바로 아래 코드는 콘솔에서 하실 때에는 주석처리 해주세요.

fetch('https://test.api.weniv.co.kr/mall') .then((productData) => productData.json()) .then((productData) => { // console.log(productData); // 콘솔에서 하실 때에는 주석처리를 풀어주세요. // codeblock-result 클래스를 가진 요소 선택 const codeblockResult = this.querySelector('.codeblock-result'); // codeblock-result 요소의 자식을 모두 제거 codeblockResult.innerHTML = ''; codeblockResult.textContent = JSON.stringify(productData); });

pre태그를 넣어서 좀 더 예쁘게 출력해보도록 하겠습니다.

fetch('https://test.api.weniv.co.kr/mall') .then((productData) => productData.json()) .then((productData) => { // pre태그 생성 const pre = document.createElement('pre'); // pre태그 안에 JSON.stringify(productData)를 넣어줌 pre.textContent = JSON.stringify(productData, null, 2); // codeblock-result 클래스를 가진 요소 선택 const codeblockResult = this.querySelector('.codeblock-result'); // codeblock-result 요소의 자식을 모두 제거 codeblockResult.innerHTML = ''; // codeblock-result 클래스를 가진 요소에 pre태그를 넣어줌 codeblockResult.appendChild(pre); });

이렇게 실행하면 콘솔에서 서버로부터 받아온 데이터를 확인할 수 있습니다. 여기서 처음에 들어온 버그를 Java라 버그잡는 개리씨 키링 개발자키링 금속키링와 같은 데이터는 실제 판매하고 있는 키링입니다. 그 뒤에 이미지도 실제 이미지입니다. 들어온 데이터는 asset/img/1/thumbnailImg.jpg입니다. 이 링크에 https://test.api.weniv.co.kr/를 붙이면 이미지를 확인할 수 있습니다. 완성된 링크는 https://test.api.weniv.co.kr/asset/img/1/thumbnailImg.jpg 입니다. 이 링크를 브라우저에 입력하면 이미지를 확인할 수 있습니다.

이미지 보기

이제 이 데이터를 가지고 실제 쇼핑몰 같은 페이지를 만들어보도록 하겠습니다. 우선 간단하게 첫번째 이미지와 텍스트만 출력해보겠습니다. 아래코드는 그대로 실행하시면 됩니다. 아래 예제이서 num의 변수만 0에서부터 1씩 증가시켜가면서 실행해보세요.

fetch('https://test.api.weniv.co.kr/mall') .then((productData) => productData.json()) .then((productData) => { const main = document.createElement('main'); const ProductCard = document.createElement('article'); const productName = document.createElement('h2'); const productPrice = document.createElement('p'); const productImg = document.createElement('img'); const num = 0; productName.textContent = `상품명 : ${productData[num].productName}`; productPrice.textContent = `가격 : ${productData[num].price}`; productImg.src = `https://test.api.weniv.co.kr/${productData[num].thumbnailImg}`; ProductCard.appendChild(productName); ProductCard.appendChild(productPrice); ProductCard.appendChild(productImg); main.appendChild(ProductCard); // codeblock-result 클래스를 가진 요소 선택 const codeblockResult = this.querySelector('.codeblock-result'); // codeblock-result 요소의 자식을 모두 제거 codeblockResult.innerHTML = ''; codeblockResult.appendChild(main); });

위 코드는 appendChild를 사용했기 때문에 product가 아래에 하나씩 붙어나가는 것을 확인할 수 있습니다. 이번에는 모든 데이터를 화면에 출력해보겠습니다. 앞애서 배운 DOM을 이용하여 받아온 JSON을 화면에 뿌려주면 됩니다. 아래코드를 실행해보세요.

fetch('https://test.api.weniv.co.kr/mall') .then((productData) => productData.json()) .then((productData) => { const main = document.createElement('main'); productData.forEach((item) => { const ProductCard = document.createElement('article'); const productName = document.createElement('h2'); const productPrice = document.createElement('p'); const productImg = document.createElement('img'); productName.textContent = `상품명 : ${item.productName}`; productPrice.textContent = `가격 : ${item.price}`; productImg.src = `https://test.api.weniv.co.kr/${item.thumbnailImg}`; ProductCard.appendChild(productName); ProductCard.appendChild(productPrice); ProductCard.appendChild(productImg); main.appendChild(ProductCard); }); // codeblock-result 클래스를 가진 요소 선택 const codeblockResult = this.querySelector('.codeblock-result'); // codeblock-result 요소의 자식을 모두 제거 codeblockResult.innerHTML = ''; codeblockResult.appendChild(main); });

페이지에 쇼핑몰 데이터가 출력된 것을 확인할 수 있습니다. 이 데이터는 실제 데이터이며 브라우저에 URL 창에 https://test.api.weniv.co.kr/mall을 입력하면 확인할 수 있습니다. 이처럼 fetch를 사용하면 서버로부터 데이터를 받아와 화면에 출력할 수 있습니다. 이를 통해 웹 서비스에서 서버와의 통신을 구현할 수 있습니다.

그런데 왜 이 코드가 Promise를 사용하는지 궁금하실 수 있습니다. 서버로부터 데이터를 받아오는 동안 다른 작업을 수행할 수 있어야 하기 때문입니다. 만약 그렇지 못하면 페이지 로딩은 한 참 후가 될 수도 있습니다. 이때 Promise를 사용하면 데이터를 받아오는 동안 다른 작업을 수행할 수 있습니다. 예를 들어 페이지의 골격을 먼저 그리고, 데이터를 받아오면 그 데이터를 화면에 출력할 수 있습니다. 그러면 사용자는 마치 홈페이지가 빠르게 로딩된 것처럼 느낄 수 있습니다.

베이스캠프 강의에서는 fetch를 데이터를 받아오는 용도로만 강의를 진행합니다. 하지만 fetch는 데이터를 읽고, 생성하고, 삭제하고, 수정하는 용도로도 사용할 수 있습니다. 이러한 작업을 CRUD(Create, Read, Update, Delete)라고 합니다. 이러한 작업을 수행할 때는 HTTP 메소드를 사용합니다. HTTP 메소드는 GET, POST, PUT, DELETE 등이 있습니다. 이러한 HTTP 메소드를 사용하여 서버와 통신할 수 있습니다. 이러한 내용은 중급자로 가기 위한 필수 개념이니 만약 초급자가 아니시라면 꼭 정리를 해두세요.

3. async, await(에이씽크, 어웨잇)

async/await는 비동기 코드를 작성하는 새로운 방법입니다. 이 문법은 ES8(ECMAScript 2017)에서 도입이 되었습니다. 기존의 비동기 처리 방식인 콜백이나 프로미스와는 다르게, 동기 코드처럼 보이게 작성할 수 있어 더 읽기 쉽고 이해하기 쉽습니다.

함수 앞에 async 키워드를 붙이면 해당 함수는 항상 프로미스를 반환합니다. 함수 내부에서 await 키워드를 사용하면, 프로미스가 처리될 때까지 함수 실행을 일시 중지시키고, 결과 값을 반환합니다.

fetch('https://test.api.weniv.co.kr/mall') .then((productData) => productData.json()) .then((productData) => { // console.log(productData); // 콘솔에서 하실 때에는 주석처리를 풀어주세요. // codeblock-result 클래스를 가진 요소 선택 const codeblockResult = this.querySelector('.codeblock-result'); // codeblock-result 요소의 자식을 모두 제거 codeblockResult.innerHTML = ''; codeblockResult.textContent = JSON.stringify(productData); });

이 코드를 우선 async/await로 변경해보겠습니다. 다만 이렇게 고쳤을 때 this가 함수를 가리키기 때문에 this 대신 document를 사용하겠습니다. 여기서 querySelectorAll('.codeblock-result')[5]querySelectorAll('.codeblock-result')[4]와 같이 변경하면 위의 실행창에 결과가 나타납니다. querySelector에서 querySelectorAll로 변경한 이유는 this로 이제 해당 요소를 선택할 수 없기 때문에 document로 접근해야 합니다.

async function getData() { const response = await fetch(`https://test.api.weniv.co.kr/mall`); const productData = await response.json(); // codeblock-result 클래스를 가진 요소 5번째 값 선택 const codeblockResult = document.querySelectorAll('.codeblock-result')[5]; // codeblock-result 요소의 자식을 모두 제거 codeblockResult.innerHTML = ''; codeblockResult.textContent = JSON.stringify(productData); } getData();

이렇게 async/await를 사용하면 비동기 코드를 동기 코드처럼 작성할 수 있어 가독성과 유지보수성이 크게 향상됩니다. 에러 처리도 일반 try/catch문을 사용할 수 있어 더 간편해집니다.

이번에는 DOM을 사용한 코드를 async/await로 변경해보겠습니다.

async function getData() { const response = await fetch(`https://test.api.weniv.co.kr/mall`); const productData = await response.json(); console.log(productData); console.log(productData.map((item) => item.productName)); console.log( productData.map((item) => item.price).filter((item) => item > 10000), ); const main = document.createElement('main'); productData.forEach((item) => { const ProductCard = document.createElement('article'); const productName = document.createElement('h2'); const productPrice = document.createElement('p'); productName.textContent = `상품명 : ${item.productName}`; productPrice.textContent = `가격 : ${item.price}`; ProductCard.appendChild(productName); ProductCard.appendChild(productPrice); main.appendChild(ProductCard); }); const codeblockResult = document.querySelectorAll('.codeblock-result')[6]; codeblockResult.innerHTML = ''; codeblockResult.appendChild(main); } getData();

async/await는 fetch 등과 비교했을 때 비교적 최근에 도입된 문법이지만, 많은 개발자들이 사용하여 현대 자바스크립트에서 비동기 프로그래밍의 핵심으로 자리잡았습니다.


참고자료

8. 비동기(콜백함수, 프로미스, await/async, fetch) 알잘딱깔센 JavaScript 비동기 프로그래밍 - 비동기 너 내 동기가 돼라
10.1 비동기 프로그래밍과 Promise11장 부록