XMLHttpRequest je zabudovaný prohlÞeÄový objekt, který umožÅuje v JavaScriptu vytváÅet HTTP požadavky.
PÅestože má v názvu slovo âXMLâ, může pracovat s libovolnými daty, nejenom s formátem XML. Můžeme odesÃlat a stahovat soubory, sledovat průbÄh a mnoho dalÅ¡Ãho.
V souÄasnosti existuje jiná, modernÄjšà metoda fetch, která XMLHttpRequest ponÄkud odsouvá do pozadÃ.
PÅi vývoji modernÃch webů se XMLHttpRequest použÃvá ze tÅà důvodů:
- Historické důvody: potÅebujeme podporovat již existujÃcà skripty obsahujÃcÃ
XMLHttpRequest. - MusÃme podporovat staré prohlÞeÄe a nechceme použÃvat polyfilly (napÅ. aby skripty zůstaly krátké).
- PotÅebujeme nÄco, co
fetchzatÃm neumÃ, napÅ. sledovat průbÄh odesÃlánÃ.
Zdá se vám to povÄdomé? Pokud ano, je to v poÅádku a můžete pokraÄovat k XMLHttpRequest. Jinak prosÃme pÅejdÄte k Fetch.
Základy
XMLHttpRequest má dva režimy práce: synchronnà a asynchronnÃ.
Nejprve se podÃváme na asynchronnÃ, který se použÃvá ve vÄtÅ¡inÄ pÅÃpadů.
K provedenà požadavku musÃme uÄinit ÄtyÅi kroky:
-
VytvoÅÃme
XMLHttpRequest:let xhr = new XMLHttpRequest();Konstruktor nemá žádné argumenty.
-
Inicializujeme ho, zpravidla hned po
new XMLHttpRequest:xhr.open(metoda, URL, [async, uživatel, heslo])Tato metoda specifikuje hlavnà parametry požadavku:
metodaâ HTTP metoda. Obvykle"GET"nebo"POST".URLâ požadovaná URL, ÅetÄzec, může to být i objekt URL.asyncâ pokud je výslovnÄ nastaven nafalse, bude požadavek synchronnÃ, probereme to zanedlouho.uživatel,hesloâ uživatelské jméno a heslo pro základnà HTTP autentifikaci (pokud jsou potÅeba).
ProsÃme vÅ¡imnÄte si, že volánÃ
openpÅes svůj název neotevÃrá spojenÃ, ale jen konfiguruje požadavek. SÃÅ¥ová aktivita zaÄÃná teprve volánÃmsend. -
Pošleme požadavek.
xhr.send([tÄlo])Tato metoda otevÃrá spojenà a posÃlá požadavek na server. Nepovinný parametr
tÄloobsahuje tÄlo požadavku.NÄkteré metody požadavků, napÅ.
GET, nemajà žádné tÄlo. Jiné metody, napÅ.POST, použÃvajÃtÄlok odeslánà dat na server. PÅÃklady uvidÃme pozdÄji. -
Nasloucháme událostem
xhr, abychom zÃskali odpovÄÄ.NejÄastÄji se použÃvajà tyto tÅi události:
loadâ když je požadavek dokonÄen (i když HTTP status je napÅ. 400 nebo 500) a celá odpovÄÄ je pÅijata.errorâ když se požadavek nepodaÅilo provést, napÅ. kvůli nefunkÄnà sÃti nebo Å¡patné URL.progressâ spouÅ¡tà se periodicky bÄhem stahovánà odpovÄdi, oznamuje, kolik bylo staženo.
xhr.onload = function() { alert(`NaÄteno: ${xhr.status} ${xhr.response}`); }; xhr.onerror = function() { // spustà se jen tehdy, když se požadavek vůbec nepovedlo provést alert(`Chyba sÃtÄ`); }; xhr.onprogress = function(událost) { // spouÅ¡tà se periodicky // událost.loaded - kolik bytů bylo staženo // událost.lengthComputable = true, pokud server poslal hlaviÄku Content-Length // událost.total - celkový poÄet bytů (je-li lengthComputable) alert(`ZÃskáno ${událost.loaded} z ${událost.total}`); };
Následuje celý pÅÃklad. Uvedený kód naÄÃtá z URL na /article/xmlhttprequest/example/load ze serveru a vypisuje průbÄh:
// 1. VytvoÅÃme nový objekt XMLHttpRequest
let xhr = new XMLHttpRequest();
// 2. Nakonfigurujeme ho: požadavek GET na URL /article/.../load
xhr.open('GET', '/article/xmlhttprequest/example/load');
// 3. PoÅ¡leme požadavek po sÃti
xhr.send();
// 4. Toto bude voláno po pÅijetà odpovÄdi
xhr.onload = function() {
if (xhr.status != 200) { // analýza HTTP statusu odpovÄdi
alert(`Chyba ${xhr.status}: ${xhr.statusText}`); // napÅ. 404: Not Found
} else { // zobrazenà odpovÄdi
alert(`Hotovo, pÅijato ${xhr.response.length} bytů`); // response je odpovÄÄ serveru
}
};
xhr.onprogress = function(událost) {
if (událost.lengthComputable) {
alert(`PÅijato ${událost.loaded} z ${událost.total} bytů`);
} else {
alert(`PÅijato ${událost.loaded} bytů`); // nenà Content-Length
}
};
xhr.onerror = function() {
alert("Požadavek neuspÄl");
};
Jakmile server odpovÃ, můžeme zÃskat výsledek z následujÃcÃch vlastnostà xhr:
status- Kód HTTP statusu (ÄÃslo):
200,404,403a podobnÄ, v pÅÃpadÄ selhánà mimo HTTP může být0. statusText- Zpráva HTTP statusu (ÅetÄzec): obvykle
OKpro200,Not Foundpro404,Forbiddenpro403a podobnÄ. response(staré skripty mohou použÃvatresponseText)- TÄlo odpovÄdi serveru.
Můžeme také specifikovat Äasový limit pomocà vlastnosti timeout:
xhr.timeout = 10000; // Äasový limit v ms, 10 sekund
Jestliže požadavek bÄhem stanovené doby neuspÄje, bude zruÅ¡en a vyvolá se událost timeout.
K pÅidánà parametrů do URL, napÅ. ?název=hodnota, a zajiÅ¡tÄnà správného kódovánà můžeme použÃt objekt URL:
let url = new URL('https://google.com/search');
url.searchParams.set('q', 'otestuj mne!');
// parametr 'q' je zakódován
xhr.open('GET', url); // https://google.com/search?q=otestuj+mne%21
Typ odpovÄdi
K nastavenà formátu odpovÄdi můžeme použÃt vlastnost xhr.responseType:
""(standardnÄ) â zÃskáme ji jako ÅetÄzec,"text"â zÃskáme ji jako ÅetÄzec,"arraybuffer"â zÃskáme ji jakoArrayBuffer(pro binárnà data, viz kapitolu ArrayBuffer, binárnà pole),"blob"â zÃskáme ji jakoBlob(pro binárnà data, viz kapitolu Blob),"document"â zÃskáme ji jako XML dokument (můžeme použÃvat XPath a jiné metody XML) nebo HTML dokument (podle MIME typu pÅijatých dat),"json"â zÃskáme ji jako JSON (automaticky se rozparsuje).
ZÃskejme napÅÃklad odpovÄÄ jako JSON:
let xhr = new XMLHttpRequest();
xhr.open('GET', '/article/xmlhttprequest/example/json');
xhr.responseType = 'json';
xhr.send();
// odpovÄÄ je {"message": "Hello, world!"}
xhr.onload = function() {
let objOdpovÄdi = xhr.response;
alert(objOdpovÄdi.message); // Hello, world!
};
Ve starých skriptech můžete najÃt i vlastnosti xhr.responseText a dokonce xhr.responseXML.
Ty existujà z historických důvodů, abychom zÃskali ÅetÄzec anebo XML dokument. V souÄasnosti bychom mÄli nastavit formát v xhr.responseType a naÄÃst xhr.response, jak je ukázáno výše.
Stavy pÅipravenosti
XMLHttpRequest bÄhem zpracovánà požadavku mÄnà svůj stav. Jeho aktuálnà stav je k dispozici v xhr.readyState.
Všechny stavy podle specifikace:
UNSENT = 0; // úvodnà stav
OPENED = 1; // voláno open
HEADERS_RECEIVED = 2; // pÅijaty hlaviÄky odpovÄdi
LOADING = 3; // odpovÄÄ se naÄÃtá (byl pÅijat datový paket)
DONE = 4; // odpovÄÄ kompletnÃ
Objekt XMLHttpRequest mezi nimi pÅecházà v poÅadà 0 â 1 â 2 â 3 â ⦠â 3 â 4. Stav 3 se opakuje pokaždé, když je ze sÃtÄ pÅijat datový paket.
Můžeme je sledovat pomocà události readystatechange:
xhr.onreadystatechange = function() {
if (xhr.readyState == 3) {
// naÄÃtánÃ
}
if (xhr.readyState == 4) {
// požadavek hotov
}
};
PosluchaÄe události readystatechange najdete v zastaralém kódu. Jsou tam z historických důvodů, jelikož v dÅÃvÄjšà dobÄ neexistovala load a jiné události. V dneÅ¡nà dobÄ je vytlaÄujà handlery load/error/progress.
Zrušenà požadavku
Požadavek můžeme kdykoli zruÅ¡it volánÃm xhr.abort():
xhr.abort(); // zrušà požadavek
TÃm se vyvolá událost abort a xhr.status se nastavà na 0.
Synchronnà požadavky
Pokud je v metodÄ open tÅetà parametr async nastaven na false, požadavek se provede synchronnÄ.
Jinými slovy, bÄh JavaScriptu se pÅi send() pozastavà a obnovà se až po pÅijetà odpovÄdi. Podobá se to pÅÃkazům alert nebo prompt.
Následuje pÅepsaný pÅÃklad, v nÄmž je tÅetà parametr open nastaven na false:
let xhr = new XMLHttpRequest();
xhr.open('GET', '/article/xmlhttprequest/hello.txt', false);
try {
xhr.send();
if (xhr.status != 200) {
alert(`Chyba ${xhr.status}: ${xhr.statusText}`);
} else {
alert(xhr.response);
}
} catch(err) { // mÃsto onerror
alert("Požadavek neuspÄl");
}
Možná to vypadá dobÅe, ale synchronnà volánà se použÃvajà jen vzácnÄ, protože blokujà JavaScript na stránce, dokud naÄÃtánà neskonÄÃ. V nÄkterých prohlÞeÄÃch pÅitom nenà možné rolovat. Jestliže synchronnà volánà trvá pÅÃliÅ¡ dlouho, prohlÞeÄ může navrhnout zavÅenà âzaseknutéâ stránky.
Pro synchronnà požadavky nenà k dispozici množstvà pokroÄilých vlastnostà XMLHttpRequest, napÅÃklad požadavek na jinou doménu nebo nastavenà Äasového limitu. NavÃc, jak vidÃte, nemůžeme sledovat průbÄh naÄÃtánÃ.
Kvůli tomu vÅ¡emu se synchronnà požadavky použÃvajà jen velmi zÅÃdka, témÄÅ vůbec. Nebudeme o nich nadále hovoÅit.
HTTP hlaviÄky
XMLHttpRequest umožÅuje posÃlat vlastnà hlaviÄky i ÄÃst hlaviÄky odpovÄdi.
Pro HTTP hlaviÄky existujà tÅi metody:
setRequestHeader(název, hodnota)-
Nastavà hlaviÄku požadavku s názvem
názevna hodnotuhodnota.PÅÃklad:
xhr.setRequestHeader('Content-Type', 'application/json');Omezenà hlaviÄekNÄkteré hlaviÄky, napÅ.
RefereraHost, jsou spravovány výluÄnÄ prohlÞeÄem. Jejich úplný seznam najdete ve specifikaci.Z důvodů bezpeÄnosti uživatele a korektnosti požadavku nemá
XMLHttpRequestdovoleno je mÄnit.Nemůžeme odstranit hlaviÄkuDalšà zvláštnostÃ
XMLHttpRequestje, že nemůžemesetRequestHeaderzruÅ¡it.Jakmile je hlaviÄka nastavena, je nastavena. Dalšà volánà pÅidajà do hlaviÄky informace, nepÅepÚà ji.
PÅÃklad:
xhr.setRequestHeader('X-Auth', '123'); xhr.setRequestHeader('X-Auth', '456'); // hlaviÄka bude: // X-Auth: 123, 456 getResponseHeader(název)-
Vrátà hlaviÄku odpovÄdi s názvem
název(kromÄSet-CookieaSet-Cookie2).PÅÃklad:
xhr.getResponseHeader('Content-Type') getAllResponseHeaders()-
Vrátà vÅ¡echny hlaviÄky odpovÄdi kromÄ
Set-CookieaSet-Cookie2.Každá hlaviÄka je vrácena na samostatném Åádku, napÅÃklad:
Cache-Control: max-age=31536000 Content-Length: 4260 Content-Type: image/png Date: Sat, 08 Sep 2012 16:53:16 GMTKonce Åádků mezi hlaviÄkami jsou vždy
"\r\n"(nezávisle na OS), takže můžeme ÅetÄzec snadno rozdÄlit na jednotlivé hlaviÄky. OddÄlovaÄ mezi názvem a hodnotou hlaviÄky je vždy dvojteÄka následovaná mezerou": ". To je pevnÄ dáno ve specifikaci.Jestliže tedy chceme zÃskat objekt obsahujÃcà dvojice název/hodnota, musÃme pÅidat krátký kód v JS.
NapÅÃklad takto (pÅedpokládáme, že pokud dvÄ hlaviÄky majà stejný název, pak druhá pÅepÃÅ¡e tu prvnÃ):
let hlaviÄky = xhr .getAllResponseHeaders() .split('\r\n') .reduce((výsledek, aktuálnÃ) => { let [název, hodnota] = aktuálnÃ.split(': '); výsledek[název] = hodnota; return výsledek; }, {}); // hlaviÄky['Content-Type'] = 'image/png'
POST, FormData
K vytvoÅenà požadavku POST můžeme použÃt zabudovaný objekt FormData.
Syntaxe:
let formData = new FormData([form]); // vytvoÅà objekt, může ho vyplnit z <form>
formData.append(název, hodnota); // pÅidá pole
VytvoÅÃme ho, můžeme ho vyplnit z formuláÅe, v pÅÃpadÄ potÅeby pÅidáme dalšà pole pomocà append a pak:
xhr.open('POST', ...)â použijeme metoduPOST.xhr.send(formData)odeÅ¡le formuláŠna server.
PÅÃklad:
<form name="osoba">
<input name="name" value="Jan">
<input name="surname" value="Novák">
</form>
<script>
// pÅedvyplnà FormData z formuláÅe
let formData = new FormData(document.forms.person);
// pÅidá jedno dalšà pole
formData.append("middle", "Leoš");
// odešle data
let xhr = new XMLHttpRequest();
xhr.open("POST", "/article/xmlhttprequest/post/user");
xhr.send(formData);
xhr.onload = () => alert(xhr.response);
</script>
FormuláŠbude poslán v kódovánà multipart/form-data.
Pokud bychom radÄji chtÄli JSON, zavoláme JSON.stringify a poÅ¡leme ho jako ÅetÄzec.
Jen nesmÃme zapomenout nastavit hlaviÄku Content-Type: application/json, mnoho programů na serverové stranÄ pÅi nà automaticky dekóduje JSON:
let xhr = new XMLHttpRequest();
let json = JSON.stringify({
jméno: "Jan",
pÅÃjmenÃ: "Novák"
});
xhr.open("POST", '/submit')
xhr.setRequestHeader('Content-type', 'application/json; charset=utf-8');
xhr.send(json);
Metoda .send(tÄlo) je znaÄnÄ vÅ¡estranná. Dokáže poslat témÄÅ jakékoli tÄlo, vÄetnÄ objektů Blob a BufferSource.
PrůbÄh odesÃlánÃ
Událost progress se spouÅ¡tà jedinÄ ve fázi stahovánÃ.
To znamená, že když nÄco posÃláme metodou POST, XMLHttpRequest nejprve odeÅ¡le naÅ¡e data (tÄlo požadavku) a pak stáhne odpovÄÄ.
Jestliže odesÃláme nÄco velkého, bezpochyby nás vÃce zajÃmá sledovánà průbÄhu odesÃlánÃ. Ale tady nám xhr.onprogress nepomůže.
Existuje jiný objekt bez metod, urÄený ke sledovánà událostà pÅi odesÃlánÃ: xhr.upload.
Generuje podobné události jako xhr, ale xhr.upload je spouÅ¡tà výhradnÄ pÅi odesÃlánÃ:
loadstartâ odesÃlánà zaÄalo.progressâ spouÅ¡tà se periodicky bÄhem odesÃlánÃ.abortâ odesÃlánà zruÅ¡eno.errorâ chyba mimo HTTP.loadâ odesÃlánà úspÄÅ¡nÄ dokonÄeno.timeoutâ vyprÅ¡el Äasový limit odesÃlánà (je-li nastavena vlastnosttimeout).loadendâ odesÃlánà skonÄilo, aÅ¥ už úspÄÅ¡nÄ nebo s chybou.
PÅÃklad handlerů:
xhr.upload.onprogress = function(událost) {
alert(`Odesláno ${událost.loaded} z ${událost.total} bytů`);
};
xhr.upload.onload = function() {
alert(`Odeslánà úspÄÅ¡nÄ dokonÄeno.`);
};
xhr.upload.onerror = function() {
alert(`Chyba pÅi odesÃlánÃ: ${xhr.status}`);
};
Následuje pÅÃklad z reálného života: odeslánà souboru se zobrazovánÃm průbÄhu:
<input type="file" onchange="upload(this.files[0])">
<script>
function upload(soubor) {
let xhr = new XMLHttpRequest();
// sledujeme průbÄh odesÃlánÃ
xhr.upload.onprogress = function(událost) {
console.log(`Odesláno ${událost.loaded} z ${událost.total}`);
};
// konec sledovánÃ: úspÄch nebo chyba
xhr.onloadend = function() {
if (xhr.status == 200) {
console.log("úspÄch");
} else {
console.log("chyba " + this.status);
}
};
xhr.open("POST", "/article/xmlhttprequest/post/upload");
xhr.send(soubor);
}
</script>
Požadavky na jiný původ
XMLHttpRequest dokáže vytváÅet požadavky na jiný původ. PoužÃvá stejnou politiku CORS jako fetch.
StejnÄ jako fetch standardnÄ neposÃlá na jiný původ cookies a HTTP autorizaci. PovolÃme to tak, že nastavÃme xhr.withCredentials na true:
let xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.open('POST', 'http://kdekoli.com/request');
...
Podrobnosti o hlaviÄkách jiného původu naleznete v kapitole Fetch: požadavky jiného původu.
ShrnutÃ
Typický kód požadavku GET s XMLHttpRequest:
let xhr = new XMLHttpRequest();
xhr.open('GET', '/my/url');
xhr.send();
xhr.onload = function() {
if (xhr.status != 200) { // HTTP chyba?
// zpracovánà chyby
alert( 'Chyba: ' + xhr.status);
return;
}
// zÃskáme odpovÄÄ z xhr.response
};
xhr.onprogress = function(událost) {
// oznámÃme průbÄh
alert(`NaÄteno ${událost.loaded} z ${událost.total}`);
};
xhr.onerror = function() {
// zpracovánà chyby mimo HTTP (napÅ. nedostupné sÃtÄ)
};
Událostà existuje ve skuteÄnosti vÃce, jejich seznam uvádà modernà specifikace (v poÅadÃ, v jakém se objevÃ):
loadstartâ požadavek zaÄal.progressâ byl pÅijat datový paket odpovÄdi, celé dosud pÅijaté tÄlo odpovÄdi je vresponse.abortâ požadavek byl zruÅ¡en volánÃmxhr.abort().errorâ nastala chyba spojenÃ, napÅ. Å¡patný název domény. Pro HTTP chyby, napÅ. 404, se nevyvolává.loadâ požadavek byl úspÄÅ¡nÄ dokonÄen.timeoutâ požadavek byl zruÅ¡en kvůli vyprÅ¡enà Äasového limitu (stane se jen tehdy, když byl limit nastaven).loadendâ spustà se poload,error,timeoutneboabort.
Události error, abort, timeout a load se vzájemnÄ vyluÄujÃ. Může nastat pouze jedna z nich.
NejÄastÄji se použÃvajà události dokonÄenà naÄÃtánà (load), selhánà naÄÃtánà (error), nebo můžeme použÃt jediný handler loadend a ovÄÅovat v nÄm vlastnosti objektu požadavku xhr, abychom vidÄli, co se stalo.
Už jsme vidÄli i jinou událost: readystatechange. Historicky se objevila pÅed dlouhou dobou, než se ustálila specifikace. V dneÅ¡nà dobÄ nenà nutné ji použÃvat, můžeme ji nahradit novÄjÅ¡Ãmi událostmi, ale ve starÅ¡Ãch skriptech ji Äasto můžeme najÃt.
Pokud potÅebujeme sledovat specificky odesÃlánÃ, mÄli bychom naslouchat stejným událostem na objektu xhr.upload.
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â¦)