XMLHttpRequest
je vestavěný objekt prohlížeče, který umožňuje provádět HTTP požadavky v JavaScriptu.
Přestože má ve svém názvu slovo „XML“, může pracovat s libovolnými daty, nejen ve formátu XML. Můžeme nahrávat/stahovat soubory, sledovat pokrok a mnoho dalšího.
Právě teď existuje jiná, modernější metoda fetch
, který poněkud zavrhuje XMLHttpRequest
.
V moderním vývoji webu XMLHttpRequest
se používá ze tří důvodů:
- Historické důvody:potřebujeme podporovat stávající skripty s
XMLHttpRequest
. - Potřebujeme podporovat staré prohlížeče a nechceme polyfilly (např. aby byly skripty malé).
- Potřebujeme něco, co
fetch
ještě neumí, např. sledovat průběh nahrávání.
Zní to povědomě? Pokud ano, pak v pořádku, pokračujte s XMLHttpRequest
. V opačném případě přejděte na Fetch.
Základy
XMLHttpRequest má dva režimy provozu:synchronní a asynchronní.
Nejprve se podívejme na asynchronní, protože se používá ve většině případů.
K provedení žádosti potřebujeme 3 kroky:
-
Vytvořte
XMLHttpRequest
:let xhr = new XMLHttpRequest();
Konstruktor nemá žádné argumenty.
-
Inicializujte jej, obvykle hned po
new XMLHttpRequest
:xhr.open(method, URL, [async, user, password])
Tato metoda specifikuje hlavní parametry požadavku:
method
– metoda HTTP. Obvykle"GET"
nebo"POST"
.URL
– URL k požadavku, řetězec, může být URL objekt.async
– pokud je explicitně nastaveno nafalse
, pak je požadavek synchronní, tím se budeme zabývat o něco později.user
,password
– přihlašovací jméno a heslo pro základní ověření HTTP (je-li vyžadováno).
Vezměte prosím na vědomí, že
open
volání, na rozdíl od svého názvu, neotevře spojení. Pouze konfiguruje požadavek, ale síťová aktivita začíná pouze volánímsend
. -
Pošlete to.
xhr.send([body])
Tato metoda otevře připojení a odešle požadavek na server. Volitelný
body
parametr obsahuje tělo požadavku.Některé metody požadavků jako
GET
nemají tělo. A některé z nich jakoPOST
použijtebody
k odeslání dat na server. Na příklady se podíváme později. -
Poslouchejte
xhr
události pro odpověď.Tyto tři události jsou nejpoužívanější:
load
– když je požadavek dokončen (i když je stav HTTP jako 400 nebo 500) a odpověď je plně stažena.error
– když požadavek nelze provést, např. výpadek sítě nebo neplatná adresa URL.progress
– spouští se pravidelně během stahování odpovědi, hlásí, kolik bylo staženo.
xhr.onload = function() { alert(`Loaded: ${xhr.status} ${xhr.response}`); }; xhr.onerror = function() { // only triggers if the request couldn't be made at all alert(`Network Error`); }; xhr.onprogress = function(event) { // triggers periodically // event.loaded - how many bytes downloaded // event.lengthComputable = true if the server sent Content-Length header // event.total - total number of bytes (if lengthComputable) alert(`Received ${event.loaded} of ${event.total}`); };
Zde je úplný příklad. Níže uvedený kód načte adresu URL na /article/xmlhttprequest/example/load
ze serveru a vytiskne průběh:
// 1. Create a new XMLHttpRequest object
let xhr = new XMLHttpRequest();
// 2. Configure it: GET-request for the URL /article/.../load
xhr.open('GET', '/article/xmlhttprequest/example/load');
// 3. Send the request over the network
xhr.send();
// 4. This will be called after the response is received
xhr.onload = function() {
if (xhr.status != 200) { // analyze HTTP status of the response
alert(`Error ${xhr.status}: ${xhr.statusText}`); // e.g. 404: Not Found
} else { // show the result
alert(`Done, got ${xhr.response.length} bytes`); // response is the server response
}
};
xhr.onprogress = function(event) {
if (event.lengthComputable) {
alert(`Received ${event.loaded} of ${event.total} bytes`);
} else {
alert(`Received ${event.loaded} bytes`); // no Content-Length
}
};
xhr.onerror = function() {
alert("Request failed");
};
Jakmile server odpoví, můžeme obdržet výsledek v následujícím xhr
vlastnosti:
status
- Stavový kód HTTP (číslo):
200
,404
,403
a tak dále, může být0
v případě selhání jiného typu než HTTP. statusText
- Stavová zpráva HTTP (řetězec):obvykle
OK
pro200
,Not Found
pro404
,Forbidden
pro403
a tak dále. response
(staré skripty mohou používatresponseText
)- Tělo odpovědi serveru.
Můžeme také určit časový limit pomocí odpovídající vlastnosti:
xhr.timeout = 10000; // timeout in ms, 10 seconds
Pokud požadavek v daném čase neuspěje, bude zrušen a timeout
spouštěče událostí.
Chcete-li do adresy URL přidat parametry, například ?name=value
a zajistíme správné kódování, můžeme použít objekt URL:
let url = new URL('https://google.com/search');
url.searchParams.set('q', 'test me!');
// the parameter 'q' is encoded
xhr.open('GET', url); // https://google.com/search?q=test+me%21
Typ odpovědi
Můžeme použít xhr.responseType
vlastnost pro nastavení formátu odpovědi:
""
(výchozí) – získat jako řetězec,"text"
– získat jako řetězec,"arraybuffer"
– získat jakoArrayBuffer
(binární data viz kapitola ArrayBuffer, binární pole),"blob"
– získat jakoBlob
(binární data viz kapitola Blob),"document"
– získat jako dokument XML (může používat XPath a další metody XML) nebo dokument HTML (na základě typu MIME přijímaných dat),"json"
– získat jako JSON (analyzováno automaticky).
Například získáme odpověď jako JSON:
let xhr = new XMLHttpRequest();
xhr.open('GET', '/article/xmlhttprequest/example/json');
xhr.responseType = 'json';
xhr.send();
// the response is {"message": "Hello, world!"}
xhr.onload = function() {
let responseObj = xhr.response;
alert(responseObj.message); // Hello, world!
};
Poznámka:
Ve starých skriptech můžete také najít xhr.responseText
a dokonce xhr.responseXML
vlastnosti.
Existují z historických důvodů, aby získali buď řetězec, nebo dokument XML. V dnešní době bychom měli nastavit formát v xhr.responseType
a získejte xhr.response
jak je ukázáno výše.
Stavy připravenosti
XMLHttpRequest
změny mezi státy, jak postupuje. Aktuální stav je přístupný jako xhr.readyState
.
Všechny stavy, jako ve specifikaci:
UNSENT = 0; // initial state
OPENED = 1; // open called
HEADERS_RECEIVED = 2; // response headers received
LOADING = 3; // response is loading (a data packet is received)
DONE = 4; // request complete
XMLHttpRequest
objekt jimi cestuje v pořadí 0
→ 1
→ 2
→ 3
→ … → 3
→ 4
. Stav 3
opakuje pokaždé, když je datový paket přijat přes síť.
Můžeme je sledovat pomocí readystatechange
událost:
xhr.onreadystatechange = function() {
if (xhr.readyState == 3) {
// loading
}
if (xhr.readyState == 4) {
// request finished
}
};
Můžete najít readystatechange
posluchači ve skutečně starém kódu, je to tam z historických důvodů, protože byly doby, kdy neexistovaly žádné load
a další akce. V současné době load/error/progress
obslužné osoby jej zavrhují.
Zrušení požadavku
Žádost můžeme kdykoli ukončit. Volání na číslo xhr.abort()
dělá to:
xhr.abort(); // terminate the request
To spustí abort
událost a xhr.status
se změní na 0
.
Synchronní požadavky
Pokud je v open
metoda třetí parametr async
je nastaven na false
, požadavek je proveden synchronně.
Jinými slovy, provádění JavaScriptu se pozastaví na send()
a pokračuje po obdržení odpovědi. Něco jako alert
nebo prompt
příkazy.
Zde je přepsaný příklad, 3. parametr open
je false
:
let xhr = new XMLHttpRequest();
xhr.open('GET', '/article/xmlhttprequest/hello.txt', false);
try {
xhr.send();
if (xhr.status != 200) {
alert(`Error ${xhr.status}: ${xhr.statusText}`);
} else {
alert(xhr.response);
}
} catch(err) { // instead of onerror
alert("Request failed");
}
Může to vypadat dobře, ale synchronní volání se používají zřídka, protože blokují JavaScript na stránce až do dokončení načítání. V některých prohlížečích není možné rolovat. Pokud synchronní hovor trvá příliš dlouho, prohlížeč může navrhnout zavření „visící“ webové stránky.
Mnoho pokročilých funkcí XMLHttpRequest
, jako je požadavek z jiné domény nebo určení časového limitu, nejsou pro synchronní požadavky k dispozici. Také, jak vidíte, žádný ukazatel průběhu.
Kvůli tomu všemu jsou synchronní požadavky využívány velmi střídmě, téměř vůbec. Už o nich nebudeme mluvit.
Hlavičky HTTP
XMLHttpRequest
umožňuje odesílat vlastní hlavičky i číst hlavičky z odpovědi.
Pro HTTP hlavičky existují 3 metody:
setRequestHeader(name, value)
-
Nastaví hlavičku požadavku s daným
name
avalue
.Například:
Omezení záhlavíxhr.setRequestHeader('Content-Type', 'application/json');
Několik hlaviček je spravováno výhradně prohlížečem, např.
Referer
aHost
.Úplný seznam je ve specifikaci.XMLHttpRequest
není dovoleno je měnit z důvodu bezpečnosti uživatele a správnosti požadavku.Další zvláštnost
XMLHttpRequest
je, žesetRequestHeader
nelze vrátit zpět .Jakmile je záhlaví nastaveno, je nastaveno. Další hovory přidávají informace do záhlaví, nepřepisujte je.
Například:
xhr.setRequestHeader('X-Auth', '123'); xhr.setRequestHeader('X-Auth', '456'); // the header will be: // X-Auth: 123, 456
getResponseHeader(name)
-
Získá hlavičku odpovědi s daným
name
(kroměSet-Cookie
aSet-Cookie2
).Například:
xhr.getResponseHeader('Content-Type')
getAllResponseHeaders()
-
Vrátí všechna záhlaví odpovědí kromě
Set-Cookie
aSet-Cookie2
.Záhlaví jsou vrácena jako jeden řádek, např.:
Cache-Control: max-age=31536000 Content-Length: 4260 Content-Type: image/png Date: Sat, 08 Sep 2012 16:53:16 GMT
Zalomení řádku mezi záhlavími je vždy
"\r\n"
(nezávisí na OS), takže jej můžeme snadno rozdělit do jednotlivých hlaviček. Oddělovačem mezi názvem a hodnotou je vždy dvojtečka následovaná mezerou": "
. To je opraveno ve specifikaci.Pokud tedy chceme získat objekt s páry jméno/hodnota, musíme do něj přidat trochu JS.
Takto (za předpokladu, že pokud mají dvě hlavičky stejný název, pak to druhé přepíše to předchozí):
let headers = xhr .getAllResponseHeaders() .split('\r\n') .reduce((result, current) => { let [name, value] = current.split(': '); result[name] = value; return result; }, {}); // headers['Content-Type'] = 'image/png'
POST, FormData
K vytvoření požadavku POST můžeme použít vestavěný objekt FormData.
Syntaxe:
let formData = new FormData([form]); // creates an object, optionally fill from <form>
formData.append(name, value); // appends a field
Vytvoříme jej, volitelně vyplníme z formuláře, append
v případě potřeby více polí a poté:
xhr.open('POST', ...)
– použijtePOST
metoda.xhr.send(formData)
k odeslání formuláře na server.
Například:
<form name="person">
<input name="name" value="John">
<input name="surname" value="Smith">
</form>
<script>
// pre-fill FormData from the form
let formData = new FormData(document.forms.person);
// add one more field
formData.append("middle", "Lee");
// send it out
let xhr = new XMLHttpRequest();
xhr.open("POST", "/article/xmlhttprequest/post/user");
xhr.send(formData);
xhr.onload = () => alert(xhr.response);
</script>
Formulář je odeslán s multipart/form-data
kódování.
Nebo, pokud se nám líbí více JSON, pak JSON.stringify
a odeslat jako řetězec.
Jen nezapomeňte nastavit záhlaví Content-Type: application/json
, mnoho serverových frameworků s ním automaticky dekóduje JSON:
let xhr = new XMLHttpRequest();
let json = JSON.stringify({
name: "John",
surname: "Smith"
});
xhr.open("POST", '/submit')
xhr.setRequestHeader('Content-type', 'application/json; charset=utf-8');
xhr.send(json);
.send(body)
metoda je docela všežravá. Může odeslat téměř jakékoli body
včetně Blob
a BufferSource
objektů.
Průběh nahrávání
progress
událost se spouští pouze ve fázi stahování.
To znamená:pokud POST
něco, XMLHttpRequest
nejprve nahraje naše data (tělo požadavku), poté stáhne odpověď.
Pokud nahráváme něco velkého, pak nás určitě více zajímá sledování průběhu nahrávání. Ale xhr.onprogress
zde nepomůže.
Existuje další objekt bez metod, který slouží výhradně ke sledování událostí nahrávání:xhr.upload
.
Generuje události podobné xhr
, ale xhr.upload
spouští je pouze při nahrávání:
loadstart
– nahrávání zahájeno.progress
– pravidelně se spouští během nahrávání.abort
– nahrávání přerušeno.error
– chyba jiná než HTTP.load
– nahrávání bylo úspěšně dokončeno.timeout
– vypršel časový limit nahrávání (pokudtimeout
vlastnost je nastavena).loadend
– nahrávání skončilo s úspěchem nebo chybou.
Příklad handlerů:
xhr.upload.onprogress = function(event) {
alert(`Uploaded ${event.loaded} of ${event.total} bytes`);
};
xhr.upload.onload = function() {
alert(`Upload finished successfully.`);
};
xhr.upload.onerror = function() {
alert(`Error during the upload: ${xhr.status}`);
};
Zde je příklad ze skutečného života:nahrávání souboru s indikací průběhu:
<input type="file" onchange="upload(this.files[0])">
<script>
function upload(file) {
let xhr = new XMLHttpRequest();
// track upload progress
xhr.upload.onprogress = function(event) {
console.log(`Uploaded ${event.loaded} of ${event.total}`);
};
// track completion: both successful or not
xhr.onloadend = function() {
if (xhr.status == 200) {
console.log("success");
} else {
console.log("error " + this.status);
}
};
xhr.open("POST", "/article/xmlhttprequest/post/upload");
xhr.send(file);
}
</script>
Požadavky napříč původy
XMLHttpRequest
může zadávat požadavky napříč původem pomocí stejné zásady CORS jako načítání.
Stejně jako fetch
, ve výchozím nastavení neposílá soubory cookie a autorizaci HTTP jinému zdroji. Chcete-li je povolit, nastavte xhr.withCredentials
na true
:
let xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.open('POST', 'http://anywhere.com/request');
...
Podrobnosti o hlavičkách mezi zdroji naleznete v kapitole Načítání:Požadavky na křížový původ.
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 error?
// handle error
alert( 'Error: ' + xhr.status);
return;
}
// get the response from xhr.response
};
xhr.onprogress = function(event) {
// report progress
alert(`Loaded ${event.loaded} of ${event.total}`);
};
xhr.onerror = function() {
// handle non-HTTP error (e.g. network down)
};
Událostí je ve skutečnosti více, moderní specifikace je uvádí (v pořadí životního cyklu):
loadstart
– požadavek byl zahájen.progress
– přišel datový paket odpovědi, celé tělo odpovědi je v tuto chvíli vresponse
.abort
– požadavek byl zrušen volánímxhr.abort()
.error
– došlo k chybě připojení, např. špatný název domény. Nestává se u chyb HTTP, jako je 404.load
– požadavek byl úspěšně dokončen.timeout
– požadavek byl zrušen z důvodu časového limitu (stane se pouze tehdy, pokud byl nastaven).loadend
– spustí se poload
,error
,timeout
neboabort
.
error
, abort
, timeout
a load
události se vzájemně vylučují. Může nastat pouze jeden z nich.
Nejpoužívanějšími událostmi jsou dokončení načítání (load
), selhání načítání (error
), nebo můžeme použít jeden loadend
handler a zkontrolujte vlastnosti objektu požadavku xhr
abyste viděli, co se stalo.
Už jsme viděli jinou událost:readystatechange
. Historicky se objevil již dávno, než se ustálila specifikace. V dnešní době ji není potřeba používat, můžeme ji nahradit novějšími událostmi, ale často ji lze nalézt ve starších skriptech.
Pokud potřebujeme konkrétně sledovat nahrávání, měli bychom poslouchat stejné události na xhr.upload
objekt.