XMLHttpRequest est un objet intĂ©grĂ© du navigateur qui permet de faire des requĂȘtes HTTP en JavaScript.
Bien quâil ait le mot âXMLâ dans son nom, il peut fonctionner sur toutes les donnĂ©es, pas seulement au format XML. Nous pouvons upload/download des fichiers, suivre les progrĂšs et bien plus encore.
Ă lâheure actuelle, il existe une autre mĂ©thode, plus moderne, fetch, qui dĂ©prĂ©cie quelque peu XMLHttpRequest.
Dans le développement Web moderne, XMLHttpRequest est utilisé pour trois raisons :
- Raisons historiques : nous devons prendre en charge les scripts existants avec
XMLHttpRequest. - Nous devons prendre en charge les anciens navigateurs et nous ne voulons pas de polyfills (par exemple pour garder les scripts minuscules).
- Nous avons besoin de quelque chose que
fetchne peut pas encore faire, par exemple pour suivre la progression de lâupload.
Cela vous semble-t-il familier ? Si oui, alors dâaccord, continuez avec XMLHttpRequest. Sinon, rendez-vous sur Fetch.
Les bases
XMLHttpRequest a deux modes de fonctionnement : synchrone et asynchrone.
Voyons dâabord lâasynchrone, car il est utilisĂ© dans la majoritĂ© des cas.
Pour faire la requĂȘte, nous avons besoin de 3 Ă©tapes :
-
Créer
XMLHttpRequest:let xhr = new XMLHttpRequest();Le constructeur nâa aucun argument.
-
Lâinitialiser, gĂ©nĂ©ralement juste aprĂšs
new XMLHttpRequest:xhr.open(method, URL, [async, user, password])Cette mĂ©thode spĂ©cifie les principaux paramĂštres de la requĂȘte :
methodâ MĂ©thode HTTP. Habituellement"GET"ou"POST".URLâ lâURL Ă demander, une chaĂźne de caractĂšres, peut ĂȘtre lâobjet URL.asyncâ si explicitement dĂ©fini surfalse, alors la demande est synchrone, nous couvrirons cela un peu plus tard.user,passwordâ identifiant et mot de passe pour lâauthentification HTTP de base (si nĂ©cessaire).
Veuillez noter que lâappel
open, contrairement Ă son nom, nâouvre pas la connexion. Il configure uniquement la demande, mais lâactivitĂ© rĂ©seau ne dĂ©marre quâavec lâappel desend. -
Lâenvoyer.
xhr.send([body])Cette méthode ouvre la connexion et envoie la demande au serveur. Le paramÚtre facultatif
bodycontient le corps de la requĂȘte.Certaines mĂ©thodes de requĂȘte comme
GETnâont pas de corps. Et certains dâentre eux commePOSTutilisentbodypour envoyer les donnĂ©es au serveur. Nous verrons des exemples de cela plus tard. -
Ăcouter les Ă©vĂ©nements
xhrpour obtenir une réponse.Ces trois événements sont les plus utilisés :
loadâ lorsque la requĂȘte est terminĂ©e (mĂȘme si lâĂ©tat HTTP est de type 400 ou 500) et que la rĂ©ponse est entiĂšrement tĂ©lĂ©chargĂ©e.errorâ lorsque la requĂȘte nâa pas pu ĂȘtre faite, par exemple rĂ©seau en panne ou URL non valide.progressâ se dĂ©clenche pĂ©riodiquement pendant le tĂ©lĂ©chargement de la rĂ©ponse, indique combien a Ă©tĂ© tĂ©lĂ©chargĂ©.
xhr.onload = function() { alert(`Loaded: ${xhr.status} ${xhr.response}`); }; xhr.onerror = function() { // ne se dĂ©clenche que si la demande n'a pas pu ĂȘtre faite du tout alert(`Network Error`); }; xhr.onprogress = function(event) { // se dĂ©clenche pĂ©riodiquement // event.loaded - combien d'octets tĂ©lĂ©chargĂ©s // event.lengthComputable = true si le serveur a envoyĂ© l'en-tĂȘte Content-Length // event.total - nombre total d'octets (si lengthComputable) alert(`Received ${event.loaded} of ${event.total}`); };
Voici un exemple complet. Le code ci-dessous charge lâURL vers /article/xmlhttprequest/example/load depuis le serveur et affiche la progression :
// 1. Créer un nouvel objet XMLHttpRequest
let xhr = new XMLHttpRequest();
// 2. Le configure : GET-request pour l'URL /article/.../load
xhr.open('GET', '/article/xmlhttprequest/example/load');
// 3. Envoyer la requĂȘte sur le rĂ©seau
xhr.send();
// 4. Ceci sera appelé aprÚs la réception de la réponse
xhr.onload = function() {
if (xhr.status != 200) { // analyse l'état HTTP de la réponse
alert(`Error ${xhr.status}: ${xhr.statusText}`); // e.g. 404: Not Found
} else { // show the result
alert(`Done, got ${xhr.response.length} bytes`); // response est la réponse du serveur
}
};
xhr.onprogress = function(event) {
if (event.lengthComputable) {
alert(`Received ${event.loaded} of ${event.total} bytes`);
} else {
alert(`Received ${event.loaded} bytes`); // pas de Content-Length
}
};
xhr.onerror = function() {
alert("Request failed");
};
Une fois que le serveur a répondu, nous pouvons recevoir le résultat dans les propriétés xhr suivantes :
status- Code dâĂ©tat HTTP (un nombre):
200,404,403et ainsi de suite, peut ĂȘtre0en cas dâĂ©chec non-HTTP. statusText- Message dâĂ©tat HTTP (une chaĂźne de caractĂšres): gĂ©nĂ©ralement
OKpour200,Not Foundpour404,Forbiddenpour403et ainsi de suite. response(les anciens scripts peuvent utiliserresponseText)- Le corps de réponse du serveur.
Nous pouvons Ă©galement spĂ©cifier un dĂ©lai dâexpiration en utilisant la propriĂ©tĂ© correspondante :
xhr.timeout = 10000; // délai d'attente en ms, 10 secondes
Si la demande Ă©choue dans le dĂ©lai imparti, elle est annulĂ©e et lâĂ©vĂ©nement timeout se dĂ©clenche.
Pour ajouter des paramĂštres Ă lâURL, comme ?name=value, et assurer le bon encodage, nous pouvons utiliser lâobjet URL :
let url = new URL('https://google.com/search');
url.searchParams.set('q', 'test me!');
// le paramÚtre 'q' est encodé
xhr.open('GET', url); // https://google.com/search?q=test+me%21
Type de réponse
Nous pouvons utiliser la propriété xhr.responseType pour définir le format de réponse :
""(default) â obtenir en tant que chaĂźne de caractĂšres,"text"â obtenir en tant que chaĂźne de caractĂšres,"arraybuffer"â obtenir en tant queArrayBuffer(pour les donnĂ©es binaires, voir le chapitre ArrayBuffer, tableaux binaires),"blob"â obtenir en tant queBlob(pour les donnĂ©es binaires, voir le chapitre Blob),"document"â obtenir en tant que document XML (peut utiliser XPath et dâautres mĂ©thodes XML) ou document HTML (basĂ© sur le type MIME des donnĂ©es reçues),"json"â obtenir en tant que JSON (analysĂ© automatiquement).
Par exemple, obtenons la réponse en JSON :
let xhr = new XMLHttpRequest();
xhr.open('GET', '/article/xmlhttprequest/example/json');
xhr.responseType = 'json';
xhr.send();
// la réponse est {"message": "Hello, world!"}
xhr.onload = function() {
let responseObj = xhr.response;
alert(responseObj.message); // Hello, world!
};
Dans les anciens scripts, vous pouvez Ă©galement trouver des propriĂ©tĂ©s xhr.responseText et mĂȘme xhr.responseXML.
Ils existent pour des raisons historiques, pour obtenir une chaßne de caractÚres ou un document XML. De nos jours, nous devons définir le format dans xhr.responseType et obtenir xhr.response comme illustré ci-dessus.
Ătats prĂȘts
XMLHttpRequest change entre les Ă©tats au fur et Ă mesure de sa progression. LâĂ©tat actuel est accessible en tant que xhr.readyState.
Tous les Ătats, comme dans la spĂ©cification:
UNSENT = 0; // état initial
OPENED = 1; // open appelé
HEADERS_RECEIVED = 2; // en-tĂȘtes de rĂ©ponse reçus
LOADING = 3; // la réponse est en cours de chargement (une donnée empaquetée est reçue)
DONE = 4; // requĂȘte terminĂ©e
Un objet XMLHttpRequest voyagent dans lâordre 0 â 1 â 2 â 3 â ⊠â 3 â 4. LâĂ©tat 3 se rĂ©pĂšte chaque fois quâun paquet de donnĂ©es est reçu sur le rĂ©seau.
Nous pouvons les suivre en utilisant lâĂ©vĂ©nement readystatechange :
xhr.onreadystatechange = function() {
if (xhr.readyState == 3) {
// chargement
}
if (xhr.readyState == 4) {
// requĂȘte terminĂ©e
}
};
Vous pouvez trouver des Ă©couteurs readystatechange dans un code trĂšs ancien, il est lĂ pour des raisons historiques, car il fut un temps oĂč il nây avait pas de load et dâautres Ă©vĂ©nements. De nos jours, les gestionnaires load/error/progress le dĂ©prĂ©cient.
Abandon de la requĂȘte
Nous pouvons mettre fin Ă la requĂȘte Ă tout moment. Lâappel Ă xhr.abort() fait cela :
xhr.abort(); // met fin Ă la requĂȘte
Cela dĂ©clenche lâĂ©vĂ©nement abort et xhr.status devient 0.
RequĂȘtes synchrones
Si dans la méthode open le troisiÚme paramÚtre async est réglé sur false, la demande est faite de maniÚre synchrone.
En dâautres termes, lâexĂ©cution de JavaScript sâinterrompt Ă send() et reprend lorsque la rĂ©ponse est reçue. Un peu comme les commandes alert ou prompt.
Voici lâexemple réécrit, le 3Ăšme paramĂštre de open est 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) { // en cas d'erreur
alert("Request failed");
}
Cela peut sembler correct, mais les appels synchrones sont rarement utilisĂ©s, car ils bloquent le JavaScript dans la page jusquâĂ la fin du chargement. Dans certains navigateurs, il devient impossible de faire dĂ©filer. Si un appel synchrone prend trop de temps, le navigateur peut suggĂ©rer de fermer la page Web âsuspendueâ.
De nombreuses capacitĂ©s avancĂ©es de XMLHttpRequest, comme la requĂȘte dâun autre domaine ou la spĂ©cification dâun dĂ©lai dâexpiration, ne sont pas disponibles pour les demandes synchrones. De plus, comme vous pouvez le voir, aucune indication de progression.
Ă cause de tout cela, les requĂȘtes synchrones sont utilisĂ©es avec parcimonie, pour ainsi dire presque jamais. Nous nâen parlerons plus.
En-tĂȘtes HTTP
XMLHttpRequest permet Ă la fois dâenvoyer des en-tĂȘtes personnalisĂ©s et de lire les en-tĂȘtes Ă partir de la rĂ©ponse.
Il existe 3 mĂ©thodes pour les en-tĂȘtes HTTP :
setRequestHeader(name, value)-
DĂ©finit lâen-tĂȘte de demande avec le
namedonné et lavalue.Par exemple :
xhr.setRequestHeader('Content-Type', 'application/json');Limites des en-tĂȘtesPlusieurs en-tĂȘtes sont gĂ©rĂ©s exclusivement par le navigateur, par exemple
RefereretHost. La liste complĂšte est dans la spĂ©cification.XMLHttpRequestnâest pas autorisĂ© Ă les modifier, pour la sĂ©curitĂ© des utilisateurs et lâexactitude de la requĂȘte.Impossible de supprimer un en-tĂȘteUne autre particularitĂ© de
XMLHttpRequestest quâon ne peut pas annulersetRequestHeader.Une fois lâen-tĂȘte dĂ©fini, il est dĂ©fini. Des appels supplĂ©mentaires ajoutent des informations Ă lâen-tĂȘte, ils ne les Ă©crasent pas.
Par exemple :
xhr.setRequestHeader('X-Auth', '123'); xhr.setRequestHeader('X-Auth', '456'); // l'en-tĂȘte sera : // X-Auth: 123, 456 getResponseHeader(name)-
Obtient lâen-tĂȘte de rĂ©ponse avec le
namedonné (saufSet-CookieetSet-Cookie2).Par exemple :
xhr.getResponseHeader('Content-Type') getAllResponseHeaders()-
Renvoie tous les en-tĂȘtes de rĂ©ponse, Ă lâexception de
Set-CookieetSet-Cookie2.Les en-tĂȘtes sont renvoyĂ©s sur une seule ligne, par exemple :
Cache-Control: max-age=31536000 Content-Length: 4260 Content-Type: image/png Date: Sat, 08 Sep 2012 16:53:16 GMTLe saut de ligne entre les en-tĂȘtes est toujours
"\r\n"(ne dĂ©pend pas du systĂšme dâexploitation), nous pouvons donc facilement le diviser en en-tĂȘtes individuels. Le sĂ©parateur entre le nom et la valeur est toujours un deux-points suivi dâun espace": ". Câest fixĂ© dans la spĂ©cification.Donc, si nous voulons obtenir un objet avec des paires nom/valeur, nous devons ajouter un peu de JS.
Comme ceci (en supposant que si deux en-tĂȘtes ont le mĂȘme nom, alors le dernier Ă©crase lâancien) :
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
Pour faire une requĂȘte POST, nous pouvons utiliser lâobjet intĂ©grĂ©e FormData.
La syntaxe :
let formData = new FormData([form]); // crée un objet, éventuellement remplir à partir de <form>
formData.append(name, value); // ajoute un champ
Nous le crĂ©ons, remplissons Ă©ventuellement Ă partir dâun formulaire, ajoutons dâautres champs si nĂ©cessaire, puis :
xhr.open('POST', ...)â utilise la mĂ©thodePOST.xhr.send(formData)pour soumettre le formulaire au serveur.
Par exemple :
<form name="person">
<input name="name" value="John">
<input name="surname" value="Smith">
</form>
<script>
// pré-remplir FormData du formulaire
let formData = new FormData(document.forms.person);
// ajouter un champ de plus
formData.append("middle", "Lee");
// l'envoie
let xhr = new XMLHttpRequest();
xhr.open("POST", "/article/xmlhttprequest/post/user");
xhr.send(formData);
xhr.onload = () => alert(xhr.response);
</script>
Le formulaire est envoyé avec un encodage multipart/form-data.
Ou, si nous aimons davantage JSON, alors JSON.stringify et lâenvoyer sous forme de chaĂźne de caractĂšres.
Nâoubliez juste pas de dĂ©finir lâen-tĂȘte Content-Type: application/json, de nombreux frameworks cĂŽtĂ© serveur dĂ©codent automatiquement JSON avec :
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);
La mĂ©thode .send(body) est assez omnivore. Il peut envoyer presque nâimporte quel body, y compris les objets Blob et BufferSource.
Progression de lâupload
LâĂ©vĂ©nement progress se dĂ©clenche uniquement Ă lâĂ©tape du tĂ©lĂ©chargement.
Câest-Ă -dire: si nous envoyons via POST quelque chose, XMLHttpRequest upload dâabord nos donnĂ©es (le corps de la requĂȘte), puis tĂ©lĂ©charge la rĂ©ponse.
Si nous uploadons quelque chose de gros, alors nous sommes sĂ»rement plus intĂ©ressĂ©s Ă suivre la progression de lâenvoi. Mais xhr.onprogress nâaide pas ici.
Il existe un autre objet, sans mĂ©thodes, exclusivement pour suivre les Ă©vĂ©nements de lâenvoi : xhr.upload.
Il gĂ©nĂšre des Ă©vĂ©nements, similaires Ă xhr, mais xhr.upload les dĂ©clenche uniquement lors de lâupload :
loadstartâ upload dĂ©marrĂ©.progressâ se dĂ©clenche pĂ©riodiquement pendant lâupload.abortâ upload annulĂ©.errorâ erreur non-HTTP.loadâ upload terminĂ© avec succĂšs.timeoutâ upload expirĂ© (si la propriĂ©tĂ©timeoutest dĂ©finie).loadendâ upload terminĂ© avec succĂšs ou erreur.
Exemple de gestionnaires :
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}`);
};
Voici un exemple réel : upload de fichier avec indication de progression :
<input type="file" onchange="upload(this.files[0])">
<script>
function upload(file) {
let xhr = new XMLHttpRequest();
// suivre la progression de l'upload
xhr.upload.onprogress = function(event) {
console.log(`Uploaded ${event.loaded} of ${event.total}`);
};
// suivi de l'envoi : réussi ou non
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>
RequĂȘtes Cross-origin
XMLHttpRequest peut faire des requĂȘtes cross-origin, en utilisant la mĂȘme politique CORS que fetch.
Tout comme fetch, elle nâenvoie pas de cookies et dâautorisation HTTP Ă une autre origine par dĂ©faut. Pour les activer, dĂ©finissez xhr.withCredentials sur true :
let xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.open('POST', 'http://anywhere.com/request');
...
Voir le chapitre Fetch: RequĂȘtes Cross-Origin pour plus de dĂ©tails sur les en-tĂȘtes cross-origin.
Résumé
Code typique de la requĂȘte GET avec 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;
}
// obtenir la réponse de xhr.response
};
xhr.onprogress = function(event) {
// report progress
alert(`Loaded ${event.loaded} of ${event.total}`);
};
xhr.onerror = function() {
// gérer les erreurs non HTTP (par exemple, panne de réseau)
};
Il y a en fait plus dâĂ©vĂ©nements, la spĂ©cification moderne les rĂ©pertorie (dans lâordre du cycle de vie) :
loadstartâ la requĂȘte a commencĂ©.progressâ un paquet de donnĂ©es de la rĂ©ponse est arrivĂ©, tout le corps de la rĂ©ponse est actuellement dansresponse.abortâ la requĂȘte a Ă©tĂ© annulĂ©e par lâappelxhr.abort().errorâ une erreur de connexion sâest produite, par exemple nom de domaine incorrect. Ne se produit pas pour les erreurs HTTP comme 404.loadâ la requĂȘte sâest terminĂ©e avec succĂšs.timeoutâ la requĂȘte a Ă©tĂ© annulĂ©e en raison du dĂ©lai dâattente (ne se produit que si elle a Ă©tĂ© dĂ©finie).loadendâ se dĂ©clenche aprĂšsload,error,timeoutouabort.
Les Ă©vĂ©nements error, abort, timeout, et load sâexcluent mutuellement. Un seul dâentre eux peut se produire.
Les Ă©vĂ©nements les plus utilisĂ©s sont la progression du chargement (load), lâĂ©chec du chargement (error), ou nous pouvons utiliser un seul gestionnaire loadend et vĂ©rifier les propriĂ©tĂ©s de lâobjet de requĂȘte xhr pour voir ce qui sâest passĂ©.
Nous avons dĂ©jĂ vu un autre Ă©vĂ©nement : readystatechange. Historiquement, il est apparu il y a longtemps, avant que la spĂ©cification ne soit rĂ©glĂ©e. De nos jours, il nâest pas nĂ©cessaire de lâutiliser, nous pouvons le remplacer par des Ă©vĂ©nements plus rĂ©cents, mais il peut souvent ĂȘtre trouvĂ© dans des scripts plus anciens.
Si nous devons suivre spĂ©cifiquement lâuplaod, alors nous devons Ă©couter les mĂȘmes Ă©vĂ©nements sur lâobjet xhr.upload.
Commentaires
<code>, pour plusieurs lignes â enveloppez-les avec la balise<pre>, pour plus de 10 lignes - utilisez une sandbox (plnkr, jsbin, codepenâŠ)