fetch
メソッドでは、*ダウンロード*の進捗状況を追跡できます。
注意: 現在、fetch
で*アップロード*の進捗状況を追跡する方法はありません。その目的には、XMLHttpRequestを使用してください。後ほど説明します。
ダウンロードの進捗状況を追跡するには、response.body
プロパティを使用できます。これはReadableStream
、つまり、ボディをチャンクごとに提供する特別なオブジェクトです。Readable Streamについては、Streams API仕様で説明されています。
response.text()
、response.json()
などのメソッドとは異なり、response.body
は読み取りプロセスを完全に制御できるため、任意の時点で消費量をカウントできます。
response.body
からレスポンスを読み取るコードの概要を以下に示します。
// instead of response.json() and other methods
const reader = response.body.getReader();
// infinite loop while the body is downloading
while(true) {
// done is true for the last chunk
// value is Uint8Array of the chunk bytes
const {done, value} = await reader.read();
if (done) {
break;
}
console.log(`Received ${value.length} bytes`)
}
await reader.read()
呼び出しの結果は、2つのプロパティを持つオブジェクトです。
done
– 読み取りが完了した場合はtrue
、そうでない場合はfalse
。value
– バイトの型付き配列:Uint8Array
。
Streams APIでは、for await..of
ループを使用したReadableStream
の非同期反復についても説明していますが、まだ広くサポートされていません(ブラウザの問題を参照)。そのため、while
ループを使用しています。
ローディングが完了するまで、つまりdone
がtrue
になるまで、ループ内でレスポンスチャンクを受信します。
進捗状況を記録するには、受信したすべてのフラグメントvalue
の長さをカウンターに追加する必要があります。
レスポンスを取得し、コンソールに進捗状況を記録する完全な動作例を以下に示します。詳細な説明は後述します。
// Step 1: start the fetch and obtain a reader
let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits?per_page=100');
const reader = response.body.getReader();
// Step 2: get total length
const contentLength = +response.headers.get('Content-Length');
// Step 3: read the data
let receivedLength = 0; // received that many bytes at the moment
let chunks = []; // array of received binary chunks (comprises the body)
while(true) {
const {done, value} = await reader.read();
if (done) {
break;
}
chunks.push(value);
receivedLength += value.length;
console.log(`Received ${receivedLength} of ${contentLength}`)
}
// Step 4: concatenate chunks into single Uint8Array
let chunksAll = new Uint8Array(receivedLength); // (4.1)
let position = 0;
for(let chunk of chunks) {
chunksAll.set(chunk, position); // (4.2)
position += chunk.length;
}
// Step 5: decode into a string
let result = new TextDecoder("utf-8").decode(chunksAll);
// We're done!
let commits = JSON.parse(result);
alert(commits[0].author.login);
それを段階的に説明しましょう
-
通常どおり
fetch
を実行しますが、response.json()
を呼び出す代わりに、ストリームリーダーresponse.body.getReader()
を取得します。同じレスポンスを読み取るために、これらの両方のメソッドを使用することはできません。リーダーを使用するか、レスポンスメソッドを使用して結果を取得します。
-
読み取る前に、
Content-Length
ヘッダーから完全なレスポンスの長さを把握できます。クロスオリジンリクエストには存在しない場合があります(Fetch: クロスオリジンリクエストの章を参照)。また、技術的にはサーバーはそれを設定する必要はありません.しかし、通常は設定されています。
-
完了するまで
await reader.read()
を呼び出します.レスポンスチャンクを配列
chunks
に収集します。これは重要です。レスポンスが消費された後、response.json()
または他の方法を使用して「再読み取り」することはできません(試してみるとエラーが発生します)。 -
最後に、
Uint8Array
バイトチャンクの配列であるchunks
があります。これらを1つの結果に結合する必要があります。残念ながら、これらを連結する単一のメソッドはないため、それを行うためのコードがいくつかあります。- 結合された長さを持つ同じ型の配列である
chunksAll = new Uint8Array(receivedLength)
を作成します。 - 次に、
.set(chunk, position)
メソッドを使用して、各chunk
を順番にコピーします。
- 結合された長さを持つ同じ型の配列である
-
chunksAll
に結果があります。ただし、これは文字列ではなくバイト配列です。文字列を作成するには、これらのバイトを解釈する必要があります。組み込みのTextDecoderはまさにそれを行います。その後、必要に応じて
JSON.parse
できます.文字列ではなくバイナリコンテンツが必要な場合はどうでしょうか?それはさらに簡単です。手順4と5を、すべてのチャンクから
Blob
を作成する1行に置き換えます。let blob = new Blob(chunks);
最後に、結果(文字列またはブロブ、どちらでも便利な方)と、その過程での進捗状況の追跡があります。
もう一度注意してください。これは*アップロード*の進捗状況(現在はfetch
では不可能)ではなく、*ダウンロード*の進捗状況のみです。
また、サイズが不明な場合は、ループ内でreceivedLength
を確認し、特定の制限に達したらループを中断する必要があります。そうすることで、chunks
がメモリをオーバーフローさせることはありません。
コメント
<code>
タグを使用します。数行のコードを挿入するには、<pre>
タグで囲みます。10行を超えるコードを挿入するには、サンドボックス(plnkr、jsbin、codepen…)を使用します。