Metoda fetch umožÅuje sledovat průbÄh stahovánÃ.
ProsÃme vÅ¡imnÄte si, že v souÄasnosti neexistuje žádný způsob, jak by fetch mohla sledovat průbÄh odesÃlánÃ. K tomuto úÄelu prosÃme použÃvejte XMLHttpRequest, který probereme pozdÄji.
Ke sledovánà průbÄhu stahovánà můžeme použÃt vlastnost odpovÄÄ.body. To je ReadableStream â speciálnà objekt, který poskytuje tÄlo po Äástech v poÅadÃ, v jakém pÅicházejÃ. Objekty ReadableStream jsou popsány ve specifikaci API Streams.
Na rozdÃl od odpovÄÄ.text(), odpovÄÄ.json() a jiných metod nám odpovÄÄ.body dává úplnou kontrolu nad procesem naÄÃtánà a my můžeme kdykoli spoÄÃtat, kolik dat se již naÄetlo.
Následuje nástin kódu, který naÄte odpovÄÄ z odpovÄÄ.body:
// mÃsto odpovÄÄ.json() a jiných metod
const reader = odpovÄÄ.body.getReader();
// nekoneÄná smyÄka, dokud se tÄlo stahuje
while(true) {
// done je true pro poslednà Äást
// value je Uint8Array s byty naÄtené Äásti
const {done, value} = await reader.read();
if (done) {
break;
}
console.log(`NaÄteno ${value.length} bytů`)
}
Výsledkem volánà await reader.read() je objekt se dvÄma vlastnostmi:
doneâtrue, když je naÄÃtánà kompletnÃ, jinakfalse.valueâ typové pole bytů:Uint8Array.
API Streams popisuje také asynchronnà iteraci nad ReadableStream pomocà cyklu for await..of, ale to zatÃm nenà široce podporováno (viz vlastnosti prohlÞeÄů), proto použÃváme cyklus while.
V cyklu dostáváme Äásti odpovÄdi, dokud naÄÃtánà neskonÄÃ, tedy než se done nastavà na true.
Abychom tento proces mohli logovat, staÄà nám pro každý zÃskaný fragment value pÅiÄÃst jeho délku k poÄÃtadlu.
NásledujÃcà plnÄ funkÄnà pÅÃklad naÄÃtá odpovÄÄ a loguje průbÄh naÄÃtánà v konzoli, dalšà vysvÄtlenà budou následovat:
// Krok 1: zaÄneme naÄÃtat a zÃskáme reader
let odpovÄÄ = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits?per_page=100');
const reader = odpovÄÄ.body.getReader();
// Krok 2: zjistÃme celkovou délku
const délkaObsahu = +odpovÄÄ.headers.get('Content-Length');
// Krok 3: naÄteme data
let obdrženáDélka = 0; // kolik bytů jsme do této chvÃle obdrželi
let Äásti = []; // pole zÃskaných binárnÃch Äástà (dohromady tvoÅà tÄlo)
while(true) {
const {done, value} = await reader.read();
if (done) {
break;
}
Äásti.push(value);
obdrženáDélka += value.length;
console.log(`NaÄteno ${obdrženáDélka} z ${délkaObsahu}`)
}
// Krok 4: spojÃme Äásti do jediného Uint8Array
let vÅ¡echnyÄásti = new Uint8Array(obdrženáDélka); // (4.1)
let pozice = 0;
for(let Äást of Äásti) {
vÅ¡echnyÄásti.set(Äást, pozice); // (4.2)
pozice += Äást.length;
}
// Krok 5: dekódujeme do ÅetÄzce
let výsledek = new TextDecoder("utf-8").decode(vÅ¡echnyÄásti);
// Jsme hotovi!
let pÅÃspÄvky = JSON.parse(výsledek);
alert(pÅÃspÄvky[0].author.login);
VysvÄtlÃme to krok za krokem:
-
Provedeme
fetchjako obvykle, ale mÃsto volánÃodpovÄÄ.json()zÃskáme reader prouduresponse.body.getReader().ProsÃme vÅ¡imnÄte si, že nemůžeme naÄÃst tutéž odpovÄÄ obÄma tÄmito metodami: pro zÃskánà výsledku použijeme buÄ reader, nebo metodu odpovÄdi.
-
PÅed naÄtenÃm můžeme zjistit délku celé odpovÄdi z hlaviÄky
Content-Length.HlaviÄka může chybÄt u požadavku jiného původu (viz kapitolu Fetch: požadavky jiného původu) a server ji technicky nemusà nastavit, ale vÄtÅ¡inou tam je.
-
Voláme
await reader.read(), dokud nejsme hotovi.Jednotlivé Äásti odpovÄdi shromažÄujeme v poli
Äásti. To je důležité, jelikož po naÄtenà odpovÄdi ji už nemůžeme naÄÃst znovu volánÃmodpovÄÄ.json()nebo jiným způsobem (můžete si to zkusit, nastane chyba). -
Nakonec tedy máme
Äástiâ pole bytových ÄástÃUint8Array. MusÃme je spojit do jediného výsledku. NaneÅ¡tÄstà neexistuje žádná metoda, která by je dokázala sama zÅetÄzit, proto k tomu použijeme krátký kód:- VytvoÅÃme
vÅ¡echnyÄásti = new Uint8Array(obdrženáDélka)â pole prvků stejného typu o celkové délce vÅ¡ech ÄástÃ. - Pak do nÄj metodou
.set(Äást, pozice)zkopÃrujeme každouÄást, jednu za druhou.
- VytvoÅÃme
-
Výsledek máme v poli
vÅ¡echnyÄásti. To je vÅ¡ak bytové pole, ne ÅetÄzec.Abychom vytvoÅili ÅetÄzec, musÃme tyto byty interpretovat. K tomu právÄ sloužà zabudovaný TextDecoder. Na ÅetÄzci pak můžeme volat
JSON.parse, pokud je to zapotÅebÃ.Co když mÃsto ÅetÄzce potÅebujeme binárnà obsah? To je jeÅ¡tÄ jednoduššÃ. NahradÃme kroky 4 a 5 jediným Åádkem, který vytvoÅÃ
Blobze vÅ¡ech ÄástÃ:let blob = new Blob(Äásti);
Nakonec máme výsledek (jako ÅetÄzec nebo blob, co se nám hodà vÃc) a sledovánà průbÄhu tohoto procesu.
Opakujeme, prosÃme vÅ¡imnÄte si, že to nefunguje pro proces odesÃlánà (to s metodou fetch v souÄasnosti nenà možné), jedinÄ pro stahovánÃ.
Dále, jestliže velikost nenà známa, mÄli bychom v cyklu kontrolovat promÄnnou obdrženáDélka, a jakmile dosáhne urÄitého limitu, ukonÄit cyklus. TÃm zajistÃme, že Äásti nepÅekroÄà dostupnou pamÄÅ¥.
KomentáÅe
<code>, pro nÄkolik Åádků je obalte znaÄkou<pre>, pro vÃce než 10 Åádků vložte odkaz na pÃskoviÅ¡tÄ (plnkr, jsbin, codepenâ¦)