fetch
metoda umožňuje sledovat stahování pokrok.
Poznámka:v současné době neexistuje žádný způsob pro fetch
sledovat nahrávání pokrok. Pro tento účel použijte XMLHttpRequest, budeme se jím zabývat později.
Ke sledování průběhu stahování můžeme použít response.body
vlastnictví. Je to ReadableStream
– speciální předmět, který poskytuje tělo kousek po kousku, jak to jde. Čitelné streamy jsou popsány ve specifikaci Streams API.
Na rozdíl od response.text()
, response.json()
a další metody, response.body
poskytuje plnou kontrolu nad procesem čtení a můžeme spočítat, kolik se spotřebuje v každém okamžiku.
Zde je náčrt kódu, který čte odpověď z 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`)
}
Výsledek await reader.read()
call je objekt se dvěma vlastnostmi:
done
–true
po dokončení čtení, jinakfalse
.value
– typované pole bajtů:Uint8Array
.
Streams API také popisuje asynchronní iteraci přes ReadableStream
s for await..of
smyčka, ale zatím není široce podporována (viz problémy s prohlížečem), takže používáme while
smyčka.
Ve smyčce dostáváme bloky odpovědí, dokud neskončí načítání, to znamená:do done
se změní na true
.
Abychom zaznamenali průběh, potřebujeme pro každý přijatý fragment pouze value
přidat jeho délku k počítadlu.
Zde je úplný pracovní příklad, který získá odpověď a zaznamená průběh v konzole, další vysvětlení, která je třeba následovat:
// 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);
Pojďme si to vysvětlit krok za krokem:
-
Provádíme
fetch
jako obvykle, ale místo voláníresponse.json()
, získáme čtečku streamůresponse.body.getReader()
.Upozorňujeme, že ke čtení stejné odpovědi nemůžeme použít obě tyto metody:k získání výsledku použijte čtečku nebo metodu odpovědi.
-
Před čtením můžeme zjistit plnou délku odezvy z
Content-Length
záhlaví.Může chybět u požadavků s křížovým původem (viz kapitola Načítání:Požadavky na křížový původ) a technicky jej server nastavovat nemusí. Ale obvykle je na místě.
-
Zavolejte na číslo
await reader.read()
dokud to nebude hotové.Shromažďujeme bloky odpovědí v poli
chunks
. To je důležité, protože po zpracování odpovědi ji nebudeme moci „znovu přečíst“ pomocíresponse.json()
nebo jiným způsobem (můžete to zkusit, dojde k chybě). -
Na konci máme
chunks
– poleUint8Array
bajtové bloky. Musíme je spojit do jediného výsledku. Bohužel neexistuje jediná metoda, která by je spojila, takže existuje nějaký kód, jak to udělat:- Vytváříme
chunksAll = new Uint8Array(receivedLength)
– pole stejného typu s kombinovanou délkou. - Potom použijte
.set(chunk, position)
způsob kopírování každéhochunk
jeden po druhém v něm.
- Vytváříme
-
Výsledek máme v
chunksAll
. Je to však bajtové pole, nikoli řetězec.Abychom vytvořili řetězec, musíme tyto bajty interpretovat. Vestavěný TextDecoder dělá přesně to. Pak můžeme
JSON.parse
v případě potřeby.Co když místo řetězce potřebujeme binární obsah? To je ještě jednodušší. Nahraďte kroky 4 a 5 jedním řádkem, který vytvoří
Blob
ze všech částí:let blob = new Blob(chunks);
Na konci máme výsledek (jako řetězec nebo blob, co je vhodné) a sledování průběhu v procesu.
Ještě jednou upozorňujeme, že to není pro nahrávání progress (nyní v žádném případě s fetch
), pouze ke stažení pokrok.
Pokud je velikost neznámá, měli bychom také zkontrolovat receivedLength
ve smyčce a zlomit ji, jakmile dosáhne určité hranice. Takže chunks
nepřeplní paměť.