코딩/Javascript

자바스크립트 fetch를 이용한 비동기 및 동기방식 프로그래밍의 이해

드리프트 2020. 12. 11. 11:25
728x170

 

 

NodeJS가 나오면서 자바스크립트는 세상을 바꿔놓을 정도의 임팩트를 보였는데요,

 

C/C++ 프로그래머라면 자바스크립트를 처음 접했을 때 비동기 방식의 프로그래밍에 적응이 힘들었습니다.

 

오늘은 자바스크립트에서 많이 쓰이는 fetch에 대해 알아 보면서 동기식/비동기식에 대해 이해하도록 하겠습니다.

 

그럼, 먼저 개발 환경을 만들어 볼까요?

 

NodeJS를 이용해서 만들도록 하겠습니다.

 

터미널에서 다음과 같이 입력하고 필요한 모듈을 설치하도록 합시다.

 

mkdir fetch-test
cd fetch-test
npm init -y
npm install xmlhttprequest --save
code .

 

fetch-test 란 폴더를 만들고 npm init으로 초기화 후 xmlhttprequest 모듈을 설치하고 VS Code를 실행하는 명령어입니다.

 

fetch란 서버에서 원하는 정보를 가져오는 것을 뜻합니다.

 

자바스크립트 초창기에는 xmlhttprequest를 써서 fetch를 했는데요,

 

브라우저는 네이티브로 XmlHttpRequest를 지원합니다.

 

그런데 NodeJS 환경에서는 네이티브로 지원을 안해서 xmlhttprequest 모듈을 따로 설치해야 합니다.

 

그래서 위에서 npm install로 설치한 겁니다.

 

그럼, 테스트를 위해 REST API 가 필요한데 제가 볼때 다음 사이트가 가장 좋은거 같습니다.

 

 jsonplaceholder.typicode.com/

 

JSONPlaceholder - Fake online REST API for testing and prototyping

When to use JSONPlaceholder is a free online REST API that you can use whenever you need some fake data. It can be in a README on GitHub, for a demo on CodeSandbox, in code examples on Stack Overflow, ...or simply to test things locally. Resources JSONPlac

jsonplaceholder.typicode.com

 

이제 본격적으로 코딩을 해볼까요?

 

 

 

 

XmlHttpRequest

 

먼저, 우리가 다른 서버에서 정보를 가져오고 싶을때 XmlHttpRequest를 이용해서 코드를 짜 보겠습니다.

 

//NodeJS라서 require로 XMLHttpRequest 모듈을 불러옵니다.
var XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;

// request로 XMLHttpRequest 객체를 만듭니다.
var request = new XMLHttpRequest();

// request("method",url)로 서버호출을 위한 파라미터를 전달합니다.
request.open("GET", "https://jsonplaceholder.typicode.com/todos/1");

// request를 요청합니다.
request.send();

// readyState 가 "DONE" 됐을 때
if (request.readyState === 4) {

// request 응답이 200 즉 "OK"됐을 때
    if(request.status === 200) {
    
    //원하는 작업을 합니다.
        console.log(request.responseText);
        const data = JSON.parse(request.responseText);
        console.log(data);
    } else {
        console.log('Error Occurred :');
    }
}

// 코드의 끝을 나타는 콘솔 로그
console.log("End of TEST");

 

코드 설명을 좀 해보자면,

 

첫번째 XMLHttpRequest를 node modules에서 로드합니다.

 

그리고 request란 객체를 만듭니다.

 

이 객체가 우리가 fetch하는 객체인데요.

 

다음으로, request.open() 으로 원하는 url을 넣어주고

 

request.send() 로 서버에 REST request를 전송합니다.

 

그러면 서버에서 응답이 와서 그 자료를 출력하면 되는데요.

 

이제 응답이 왔는지 체크하기 위해서 request.readyState === 4 라는 if 문을 쓰는데요.

 

여기서 숫자 4는 request 가 'DONE"이 됐다는 뜻입니다.

 

그리고 request.status === 200 은 HTTP 상태 코드 200일때 즉, OK일때 란 뜻입니다.

 

따라서, request 전송이랑 응답이 OK 일때 우리는 request.responseText로 원하는 정보를 JSON Object로 받습니다.

 

그리고, JSON 파싱해서 화면에 뿌려주는 코드인데요.

 

한번 실행해 볼까요?

 

 

 

위 스크린샷을 보시면 우리는 분명히 node xmlhttp-test.js 로 실행을 했는데 결과가 없습니다.

 

C/C++ 프로그래머라면 분명히 이해가 안갈텐데요. 자바스크립트에서는 위 코드는 잘못된 코드입니다.

 

왜냐하면 서버에서 데이터를 가져오는 XmlHttpRequest는 하나의 커다란 이벤트 루프속에서 돌고 있기 때문에

 

C/C++처럼 순차적인 프로그래밍으로는 원하는 결과를 얻을 수 없습니다.

 

위에서 request.send() 를 보냈을때 응답이 올때까지 기다리지 않고 바로 다음 코드를 실행해 버리기 때문에

 

응답이 뒤늦게 오더라도 프로그램은 벌써 마지막 행을 실행하고 끝나 버리죠.

 

 

 

 

 

 

우리가 프로그래밍을 잘못한 이유는 XmlHttpRequest가 응답을 리턴했을 때를 처리해 주는 함수를 만들어 줬어야 합니다.

 

즉, 콜백함수(callback) 를 만들어 줬어야 하는 거죠.

 

이 콜백함수를 만들어 주면 NodeJS는 콜백함수를 실행한 후에 종료하게 됩니다.

 

그래서 우리가 원하는 응답을 얻게 되죠.

 

그럼 콜백함수 형태로 바꿔보겠습니다.

 

var XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;

var request = new XMLHttpRequest();

// onreadystatechange 라는 이벤트에 콜백함수를 연결시켜 줬습니다.
// onreadystatechange 이벤트가 발생하면 꼭 콜백함수를 실행하게 됩니다.
request.onreadystatechange = function () {
    if (request.readyState === 4) {
        if(request.status === 200) {
            console.log(request.responseText);
            const data = JSON.parse(request.responseText);
            console.log(data);
        } else {
            console.log('Error Occurred :');
        }
    }
}
request.open('GET', 'https://jsonplaceholder.typicode.com/todos/1');
request.send();

console.log("End of TEST");

 

위 코드에서 눈여겨봐야할 곳은 request.onreadystatechange 입니다.

 

onreadystatechange 는 이벤트인데요, 이 이벤트가 발동하는 때는 request의 상태가 변했을 때입니다.

 

우리는 콜백함수를 onreadystatechange 이벤트에 등록할 수 있는데요.

 

그러면 request의 상태가 변했을 때 작동되는 콜백함수를 만들 수 있습니다.

 

이제 콜백함수안에 코드를 살펴 볼까요?

 

request.readyState === 4 라는 if문으로 request가 "DONE" 됐을 때,

 

그리고 request.status === 200 이라는 if문으로 request 응답이 OK 됐을 때,

 

원하는 데이타를 파싱해서 출력해주는 형태입니다.

 

이제 위 코드를 실행해 볼까요?

 

 

request가 성공해서 원하는 데이터가 나온 결과 입니다.

 

여기서 비동기식 방식을 이해할 수 있는거는 "End of TEST"라는 문구는 코드의 제일 마지막에 있는데도

 

request 응답보다 먼저 실행됐다는 겁니다.

 

console.log() 는 동기식으로 순차적으로 실행됐고, 코드의 마지막에서 비동기식 콜백함수의 실행을 기다린 모양새가 된겁니다.

 

그래서 "End of TEST" 문구가 먼저 출력된 겁니다.

 

위와 같은 콜백함수는 가장 쉬운 케이스인데요. 콜백이 중첩되는 경우 흔히 얘기하는 디버깅도 안되는 콜백지옥에 빠지게 되는 겁니다.

 

지금까지, XmlHttpRequest를 이용해서 서버에서 DATA를 fetch하는 방법에 대해 알아 보았습니다.

 

두번째 알아볼 방식은 최신 자바스크립에 네이티브하게 포함되어 있는 fetch()함수를 사용해서 서버로 request해 보겠습니다.

 

 

 

Fetch

 

Fetch는 오늘날 대부분의 브라우저에서 지원하는 기본 JavaScript API입니다.

 

Fetch를 사용하면 XMLHttpRequest와 유사한 네트워크 요청을 만들 수 있습니다.

 

Google Developers Documentation에 따르면 Fetch를 사용하면 이전 XMLHttpRequest보다 더 쉽게 비동기 요청을 만들고 응답을 더 잘 처리 할 수 있습니다.

 

XMLHttpRequest API보다 개선 된 것입니다.

 

Fetch와 XMLHttpRequest의 주요 차이점은 Fetch API가 Promise를 사용하므로 콜백 지옥을 피한다는 것입니다.

 

Fetch API에는 다음과 같은 인터페이스가 있습니다.

 

  • fetch () : 리소스를 가져 오는 데 사용되는 fetch () 메서드입니다.
  • Headers : 응답 / 요청 헤더로 이것을 쿼리하고 결과에 따라 다른 작업을 수행 할 수 있습니다.
  • Request : 리소스 요청을 나타냅니다.
  • Response : 요청에 대한 응답을 나타냅니다.

 

fetch()를 사용하여 요청하기

 

fetch() 함수는 가져 오려는 리소스에 대한 url을 필요합니다.

 

fetch()는 성공 여부에 관계없이 Promise를 반환하는데, 요청이 성공하면 .then() 함수를 통해 Response 객체를 받고, 요청이 실패하면 .catch () 함수는 오류 객체를 받습니다.

 

우리는 NodeJS환경에서 테스트를 하기 때문에 node-fetch 모듈을 설치해야 합니다.

 

브라우저상에서는 네이티브하게 지원되지만 NodeJS 사용환경이라서 node-fetch를 설치하는 겁니다.

 

npm install node-fetch --save

 

일단 코드를 볼까요?

 

const fetch = require('node-fetch');

fetch('https://jsonplaceholder.typicode.com/todos/1')
    .then(function (response) {
        console.log(response);
    })
    .catch(function (err) {
        console.log("Something went wrong!", err);
    });

 

일단 XMLHttpRequest보다  훨씬 간단해졌습니다.

 

fetch()에 url 하나만 넣었는데, 그 다음이 이해 하기 어려운데요.

 

자바스크립트에 Promise란게 나왔습니다.

 

Promise란 말 뜻데로 약속인데요, fetch()함수를 실행하면 무조건 약속을 지키라는 뜻에서 Promise객체를 반환합니다.

 

그러면 Promise 객체를 어떻게 체크할까요?

 

그 방법은 fetch(url).then(function(response) {}) 방식입니다.

 

즉, chain reaction 방식인데요. .then() 안쪽에 콜백함수를 넣는 방식입니다.

 

에러일때는 .catch(function(err){}) 쪽에 콜백함수를 넣습니다.

 

그럼, 실행 결과를 볼까요?

 

 

뭔가 어려운데요.

 

.then() 콜백함수에서 response 인자를 사용했는데 response는 Response객체를 나타냅니다.

 

그 객체를 그냥 console.log하니까 객체를 보여주는 경우가 된겁니다.

 

그러면 우리가 원하는 코드는 어떻게 작성할까요?

 

const fetch = require('node-fetch');

fetch('https://jsonplaceholder.typicode.com/todos/1')
    .then(function (response) {
        return response.json();
    })
    .then(function (data) {
        console.log(data);
    })
    .catch(function (err) {
        console.log("Something went wrong!", err);
    });

 

보시면 .then을 한번 더 했습니다.

 

즉 첫번째 Promise가 오면 첫번째 .then 콜백함수에서 response.json() 함수를 이용해서 JSON 파싱하고 그걸 리턴하면 두번째 .then()에서 data라는 인자로 콘솔 로그하는 겁니다.

 

결과를 볼까요?

 

 

우리가 원하는 결과데로 깔끔하게 나옵니다.

 

 

 

Async / Await

 

Promise를 사용하는 다른 방법은 Async랑 Await 를 방법입니다.

 

일단 코드를 보시죠?

 

const fetch = require('node-fetch');

async function getTodos(url) {
    try {
        const response = await fetch(url);
        const json = await response.json();
        console.log(json);
    } catch (err) {
        console.log(err);
    }
}

getTodos('https://jsonplaceholder.typicode.com/todos/1');

 

코드가 약간 바꼈는데요.

 

일단 getTodos() 함수에 앞에 async 키워드가 선언되었습니다.

 

이 말은 getTodos()함수는 async 즉, 비동기식으로 실행하라고 지시한겁니다.

 

맨 밑에서 getTodos() 를 실행하면 이 함수는 비동기식으로 지정됐기 때문에 비동기식으로 실행됩니다.

 

이제 getTodos() 함수안을 볼까요?

 

try {} catch {} 함수를 이용해서 에러 체크를 합니다.

 

일단 try 블록안에 원하는 코드를 넣는데요.

 

fetch(url) 함수를 실행할때 앞에 await 키워드를 넣었습니다.

 

즉, 비동기 실행을 기다리고 있으라고 지시한겁니다.

 

비동기 실행이 완료되면 response 변수에 저장되고

 

그 다음 코드도 await 비동기식 실행을 지정해서 json 파싱을 한겁니다.

 

즉, response.json()은 fetch(url)이 실행되야 의미있기 때문에 똑같이 await 를 지정했습니다.

 

실행 결과는 아래와 같습니다.

 

 

fetch() 함수를 사용했을 때와 결과는 같습니다.

 

 

지금까지 자바스크립트에서 fetch하는 방법을 알아 보았는데요.

 

개인적으로 두번째가 직관적인데 세번째 방식이 요즘은 대세라고 하더군요.

 

각자 편한데로 쓰시면 됩니다.

 

그럼. 이만.

 

그리드형