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â¦)