네트워크를 통해 서버로부터 데이터를 요청하고 가져오는 Ajax Asynchronous Javascript and XML 기법은 XML형식을 이용한 비동기적 정보 교환방법입니다. 이를 통해 데이터를 기반으로 하는 동적인 웹을 만들 수 있어요. AJAX는 페이지 전체를 로드하지 않고 일부의 DOM만 업데이트 할 수 있도록 하는 특징 덕에 현대 웹에서는 즐겨 사용하는 기법입니다. AJAX를 사용하기 위해, 정통적으로는 XMLHttpRequest 객체를 생성하여 요청하였지만 이는 가독성이 나쁘다는 이유로 현재는 브라우저에서 제공하는 fetch API 로 변경되었습니다.

fetch API는 이벤트 기반인 XMLHttpRequest 방식과 달리 Promise를 기반으로 구성되어 비동기 처리 방식에 더 적합합니다. 하지만 아시다시피, fetch에서는 응답을 보내고 받기 위한 await를 사용하고, 이를 가공하기 위해 반드시 한번 더 await를 사용하게 되죠.

const res = await fetch('./gateway');
const data = await res.json();
//const text = await rest.text();

하지만 이상합니다. 우리가 아는 json은 비동기 메서드가 아니지 않나요?

const string = '{"key":"value"}';
const obj = JSON.parse(string);

console.log(obj);

우리는 이미 fetch 메서드로 응답을 가져와서 전달해주었는데, 왜 비동기 메서드가 아닌 json 을 호출할 때에도 await를 붙여야 하는 걸까요?


다음은 Fetch API에 대한 mdn 문서의 일부입니다.

The fetch() method takes one mandatory argument, the path to the resource you want to fetch. It returns a Promise that resolves to the Response to that request — as soon as the server responds with headers — even if the server response is an HTTP error status. You can also optionally pass in an init options object as the second argument. 서버는 HTTP error 상태여도 header와 함께 응답을 한다고 하네요. Once a Response is retrieved, there are a number of methods available to define what the body content is and how it should be handled. 응답이 되돌아오고 나면 다양한 방법으로 body의 content를 다룰 수 있다고 합니다.

await를 두 번 써야 하는 이유에 대한 정답은…

아래의 코드에서는 header를 가져오게 되고,

const res = await fetch('./gateway');

아래의 코드에서는 body를 가져오게 되기 때문입니다.

const data = await res.json();

body는 header에 비해 크기가 크기 때문에 조금 더 기다려야 하므로 header를 먼저 가져 와 응답을 하도록 하는 것입니다.

아래는 Node.js 로 작성한 평범한 서버측 코드입니다. 이 코드에서는 일부러 body의 content를 한번에 1바이트/2초의 속도로 보내도록 하였습니다.

const http = require("http")
const fs = require("fs")
const path = require("path")

/**
 * A simple server that returns a webpage,
 * or streams json from a file to the client.
 */
const server = http.createServer((req, res) => {
    // serve HTML
    if (req.method === "GET" && req.url === "/") {
        const filePath = path.join(__dirname, "index.html")
        fs.readFile(filePath, (_, data) => {
            res.writeHead(200, { "Content-Type": "text/html" })
            res.end(data)
        })

        return
    }

    // serve JSON, but _slowly_
    if (req.method === "GET" && req.url === "/json") {
        res.writeHead(200, { "Content-Type": "application/json" })

        // set up a readable stream
        const filePath = path.join(__dirname, "data.json")
        const stream = fs.createReadStream(filePath, { encoding: "utf8" })

        // read the stream on byte (character) at a time and send it to the client
        stream.on("readable", function () {
            const interval = setInterval(() => {
                const chunk = stream.read(1)
                if (chunk !== null) {
                    res.write(chunk)
                } else {
                    clearInterval(interval)
                    res.end()
                }
            }, 2) // <--- slow!
        })

        return
    }

    // whoops, someone wants something we don't have
    res.writeHead(404, { "Content-Type": "text/plain" })
    res.end("Not Found")
})

const PORT = 3000
server.listen(PORT, () => {
    console.log(`Server running at <http://localhost>:${PORT}/`)
})