69
30

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

なぜ JavaScript の Fetch API は2回 await しないとJSONが取れないのか?

Last updated at Posted at 2022-12-07

疑問

なぜ JavaScript の Fetch API はわざわざ fetch(...) で直接JSONなどのレスポンスのPromiseを返さずに、 わざわざ2回awaitさせてJSONを返すのでしょうか?

非同期構文の助けがあるとはいえ、いちいちPromiseにされると面倒です。

// なぜ await を2回も呼ぶ必要があるのか?
const json = await (await fetch("/api/v1/foobar")).json();

結論からいうと、サーバがレスポンスのヘッダ部を返した段階で即時に処理(継続)を再開できるようにするためです。

たとえば、下記のコードにおいて、サーバ側でレスポンス用のHTTPのヘッダをフラッシュした段階で console.log("サーバが少なくともヘッダを返した"); が実行されるということですね。

const res = await fetch("/api/v1/foobar");
console.log("サーバが少なくともヘッダを返した");

const json = await res.json();
console.log("サーバがボディ(JSON)も全部返した");

Node.js で実験してみる

本当にそうなっているのでしょうか? 実験してみましょう。 (Node.js v18.4.0)

server.js
// $ node server.js
// # => http://localhost:18080

const http = require("http");

const sleep = (ms) => new Promise((done) => setTimeout(done, ms));

http
  .createServer(async (req, res) => {
    // リクエストを受け取って2秒スリープ
    await sleep(2000);

    // ヘッダを書き出して2秒スリープ
    res.setHeader("X-MyHeader-Value", "MY HEADER!!!!");
    res.setHeader("Access-Control-Allow-Origin", "*");
    res.setHeader("Access-Control-Expose-Headers", "*");
    res.flushHeaders();
    await sleep(2000);

    // ボディを書き出す
    res.write(
      JSON.stringify({
        message: "Hello delayed message!",
      })
    );
    res.end();
  })
  .listen(18080);
client.js
const task = async () => {
  const req = fetch("http://localhost:18080");
  console.log(Date(), "Request");
  const res = await req;
  console.log(Date(), "Headers", JSON.stringify([...res.headers.entries()]));
  const json = await res.json();
  console.log(Date(), "Body", json);
};

task();
$ node client.js
Mon Dec 05 2022 16:58:54 GMT+0900 (日本標準時) Request
Mon Dec 05 2022 16:58:56 GMT+0900 (日本標準時) Headers [["access-control-allow-origin","*"],["access-control-expose-headers","*"],["connection","keep-alive"],["date","Mon, 05 Dec 2022 07:58:56 GMT"],["keep-alive","timeout=5"],["transfer-encoding","chunked"],["x-myheader-value","MY HEADER!!!!"]]
Mon Dec 05 2022 16:58:58 GMT+0900 (日本標準時) Body { message: 'Hello delayed message!' }

Node.js で fetch を動かしたところ、たしかに下記のようになっているので、

  • 16:58:54: リクエストを発した時刻
  • 16:58:56: ヘッダを受け取った時刻
  • 16:58:58: JSONを受け取った時刻

きちんとヘッダをフラッシュした段階で最初のPromiseが解決され、2秒後のボディの書き込みで続くJSON化の処理が動いていることがわかりました。

ブラウザで実験してみる

ブラウザ (Google Chrome 108) でも同様の動きになるか試してみます。すでにサーバーのコードでCORSが有効になっているので同様のクライアントコードを動かしてみます。

Mon Dec 05 2022 16:58:07 GMT+0900 (日本標準時) Request
Mon Dec 05 2022 16:58:09 GMT+0900 (日本標準時) Headers [["access-control-allow-origin","*"],["access-control-expose-headers","*"],["connection","keep-alive"],["date","Mon, 05 Dec 2022 07:58:09 GMT"],["keep-alive","timeout=5"],["transfer-encoding","chunked"],["x-myheader-value","MY HEADER!!!!"]]
Mon Dec 05 2022 16:58:11 GMT+0900 (日本標準時) Body {message: 'Hello delayed message!'}

image.png

きちんと2秒差でJSONの内容が表示されました。

まとめ

Fetch APIの await はヘッダがフラッシュされた段階で一旦抜ける柔軟な仕様ということがわかりました。

69
30
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
69
30

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?