XMLHttpRequest

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ů:

  1. Historické důvody:potřebujeme podporovat stávající skripty s XMLHttpRequest .
  2. Potřebujeme podporovat staré prohlížeče a nechceme polyfilly (např. aby byly skripty malé).
  3. 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:

  1. Vytvořte XMLHttpRequest :

    let xhr = new XMLHttpRequest();

    Konstruktor nemá žádné argumenty.

  2. 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 na false , 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ím send .

  3. 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 jako POST použijte body k odeslání dat na server. Na příklady se podíváme později.

  4. 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ýt 0 v případě selhání jiného typu než HTTP.
statusText
Stavová zpráva HTTP (řetězec):obvykle OK pro 200 , Not Found pro 404 , Forbidden pro 403 a tak dále.
response (staré skripty mohou používat responseText )
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í.

Parametry vyhledávání URL

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 jako ArrayBuffer (binární data viz kapitola ArrayBuffer, binární pole),
  • "blob" – získat jako Blob (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í 0123 → … → 34 . 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 a value .

Například:

xhr.setRequestHeader('Content-Type', 'application/json');
Omezení záhlaví

Několik hlaviček je spravováno výhradně prohlížečem, např. Referer a Host .Úplný seznam je ve specifikaci.

XMLHttpRequest není dovoleno je měnit z důvodu bezpečnosti uživatele a správnosti požadavku.

Záhlaví nelze odstranit

Další zvláštnost XMLHttpRequest je, že setRequestHeader 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 a Set-Cookie2 ).

Například:

xhr.getResponseHeader('Content-Type')
getAllResponseHeaders()

Vrátí všechna záhlaví odpovědí kromě Set-Cookie a Set-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é:

  1. xhr.open('POST', ...) – použijte POST metoda.
  2. 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í (pokud timeout 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 v response .
  • abort – požadavek byl zrušen voláním xhr.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 po load , error , timeout nebo abort .

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.