Fetch APIの使い方といえば
const response = await fetch("https://api.example.com/");
const json = await response.json();
のように、レスポンスオブジェクトを得るためにまずawait、そしてさらにそこからデータを取り出すためにもう一度Promiseの解決を待つ必要がある。これはなぜなのか。少し考えれば当たり前の話なのだが、日本語の記事が見つからなかったので書いておく。
ちなみにaxiosとかだと
import axios from "axios";
const json = await axios.get("https://api.example.com/");
これだけ。JSONのパースも暗黙的に行われるし、レスポンスヘッダがエラーだったらPromiseはリジェクトされる。うーんeasy。さすがVueの標準なだけのことはある。
今まであまり考えずに使ってきたが、よくよく考えれば当たり前だった。
fetchメソッドのMDNに詳しい説明が書いてある。
fetch()
はWindowOrWorkerGlobalScope
ミックスインのメソッドで、ネットワークからリソースを取得するプロセスを開始し、レスポンスが利用できるようになったら満足するプロミスを返します。このプロミスはリクエストに対するレスポンスを表すResponse
で解決します。プロミスは HTTP エラーでは拒否されず、ネットワークエラーでのみ拒否されます。 HTTP エラーをチェックするには、 then ハンドラーを使用する必要があります。
簡潔で明確な説明である。Promiseがfullfilledになるのは、「レスポンスが利用できるようになったら」、つまりHTTPのレスポンスヘッダまで受信を終えてレスポンスボディの受信を開始するタイミングということなのだろう。
そして、fetchメソッドの戻り値に使用されているResponseオブジェクトは、Bodyを実装している。このオブジェクトはRequestオブジェクトにも実装されているが、その中核はbodyプロパティとしてアクセス可能な単なるReadableStreamだ。
ただ、Bodyにはリクエストボディやレスポンスボディを処理するための便利メソッドがたくさん用意されている。それがお馴染みのjsonやtextメソッドだ。ただ、JSONや文字列を返すためには、先にbodyのReadableStreamをメモリ上に最後まで読み切る必要がある。このため、これらのメソッドの戻り値はPromiseだったのだ。
Body.json()
Response ストリームを取得し、完全に読み込む。 ボディのテキストを JSON として解析した結果で解決する promise を返す。Body.text()
Response ストリームを取得し、完全に読み込む。 USVString(テキスト)で解決する promise を返す。 レスポンスは常に UTF-8 でデコードする。
メモリの制限にかかるくらい大量のデータを扱う必要がある場合はBody#textやBody#arrayBufferよりもBody#bodyを直接使ったほうがよさそうだ。