Requêtes asynchrones avec l’objet XHR

R

Les technologies AJAX sont apparues il y a une dizaine d’années et sont aujourd’hui très répandues sur de nombreux sites internet en raison, notamment, de la souplesse qu’elles confèrent aux interfaces graphiques ainsi qu’à l’amélioration considérable de l’expérience utilisateur qui en découle.

AJAX, requêtes asynchrones, objet XHR … que veulent dire ces termes finalement très utilisés ?!

Que signifie AJAX ?

C’est un terme un peu passe-partout – très sexy, qui fait référence à un géant de la mythologie grecque qu’il est très très fort, et c’est sans doute ce qui fait qu’il a été récupéré et reste employé aujourd’hui. Club de football, produits ménagers, informatique … on l’a mis un peu à toutes les sauces autour d’un vecteur commun : symboliser la puissance.

En informatique, AJAX est un acronyme qui signifie Asynchronous JavaScript And XML, JavaScript et XML asynchrones, en français dans le texte. Ce n’est pas une technologie ni un langage : sous le nom AJAX, on regroupe en fait plusieurs technologies (DOM, HTML, CSS, XML, JSON) qui, ensemble, permettent de réaliser et traiter des requêtes asynchrones, pas seulement avec l’objet XMLHttpRequest mais bien en collaboration totale ou partielle les unes avec les autres.

Qu’est-ce que l’objet XMLHttpRequest ?

XMLHttpRequest, souvent rencontré sous l’acronyme XHR, est initialement un objet ActiveX mis en place par Microsoft en 1998 et ensuite implémenté dans la norme EcmaScript. Il permet, grâce à une requête HTTP, d’obtenir des données sérialisées au format XML ou JSON, mais aussi au format textuel, et ce de manière synchrone ou asynchrone. Supporté dans sa version deux depuis IE >= 10, il est bien implémenté par tous les navigateurs modernes.

Qu’est-ce qu’une requête asynchrone ?

Par opposition à un phénomène synchrone, un phénomène asynchrone est caractérisé par un décalage temporel : une requête asynchrone peut être réalisée en tâche de fond sans rechargement de page. Et c’est bien cette possibilité qui rend les requêtes asynchrones si populaires. Pour que cela soit possible, elle peut être lancée et exécutée en tâche de fond tandis que le reste des instructions continue à être exécuté.

Pour simplifier, une requête synchrone pourrait être comparée à une file d’attente, où chacun passe à son tour, tandis qu’une requête asynchrone commence ce qu’elle a à faire et fait ensuite un pas de côté pour laisser passer et ne pas faire attendre les autres. C’est altruiste, une requête asynchrone.

Avertissement

Le recours au technologies asynchrones pose des questions en matière d’accessibilité et de référencement. Il existe, la plupart du temps, des solutions de contournement et le propos n’est pas d’affoler la ménagère.  Notez simplement que lorsque vous faites de l’AJAX, vous risquez parfois de rendre certains contenus inacessibles ou versatiles, et que c’est un état de fait dont il faut tenir compte dans vos développements.

Ceci étant dit, avant d’entrer dans le vif du sujet et de passer à la démonstration par l’exemple, il est intéressant de prendre connaissance des propriétés et méthodes principales exposées par l’objet XMLHttpRequest. Vous pouvez trouver la documentation complète XHR 2 sur le site du W3C.

Propriétés de l’objet XMLHttpRequest

Propriété Description
onreadystatechange Evénement qui encapsule une fonction de callback qui sera appelée à chaque changement d’état de la propriété readyState
readyState Contient le statut de l’instance XMLHttpRequest
responseText Retourne un type string contenant les données renvoyées par le serveur
responseXML Retourne un type XML contenant les données renvoyées par le serveur
status Retourne le code retour de la requête HTTP (ex : 200, 404, …)
statusText Retourne le statut de la requête sous sa forme textuelle (ex : ‘OK’, ‘Not Found’, …)

Etats possibles d’un instance HXR

Etat/Valeur Signification
UNSENT/0 L’objet xhr a été créé mais pas initialisé (la méthode open() n’a pas encore été appelée)
OPENED/1 La méthode open() a été appelée mais la requête n’a pas encore été envoyée
HEADERS_RECEIVED/2 La méthode send() a été appelée et les données ont été envoyées au serveur
LOADING/3 Le serveur traite les informations et a commencé à envoyer les données, entêtes reçus
DONE/4 Toutes les données ont été réceptionnées

Méthodes de l’objet XMLHttpRequest

Méthode Description
abort() Met fin à la requête courante et réinitialise l’instance XHR
getAllResponseHeaders() Retourne l’ensemble des headers
getResponseHeader() Retourne le header passé en paramètre
open() Prépare une requête à éxécuter. Prend 5 arguments et spécifie ainsi la méthode de la requête, l’URL à atteindre, le mode de la requête, un nom d’utilisateur et un mot de passe. Le premier argument prend la valeur “GET” ou “POST”, le second une chaîne de caractères de type URL (autres protocoles que http possibles), le troisième un booléen indiquant si on est en mode asynchrone ou non (true par défaut) et enfin deux paramètres optionnels à n’utiliser qu’en cas d’authentification.
send() Envoie la requête de type string passée en paramètre en cas d’utilisation de la méthode POST
setRequestHeader() Ajoute un en-tête spécifique contenant le type MIME et sa valeur en cas d’utilisation de la méthode POST

Comment réaliser une requête asynchrone avec l’objet XHR ?

Nous allons nous contenter d’un exemple simplissime pour découvrir l’objet XHR et ce qu’il permet de faire, en réalisant une requête asynchrone sur un fichier texte afin d’en afficher le contenu sur notre site sans passer par aucun script côté serveur.

Dans le cadre d’une utilisation plus réaliste, d’autres traitements plus conséquents sont à envisager. Vous trouverez à la fin de cet article un lien vers un tutoriel pour une utilisation avancée des requêtes asynchrones.

Instancier un objet XHR

La première chose à faire est de créer une instance de l’objet XHR. Pour ce faire, afin d’assurer la compatibilité de la requête sur un maximum de navigateurs, il faut utiliser un polyfill, que voici :

function getXmlHttpRequest() {
  var xhr;
  // Si l'objet XMLHttpRequest est implémenté (navigateurs standards et IE >= 10)
  if(window.XMLHttpRequest) {
    return xhr = new XMLHttpRequest();
  }
  // Si l'objet ActiveXObject est implémenté (IE <= 9)
  else if(window.ActiveXObject) {
    // On sait qu'IE a changé son implémentation de l'objet XHR en cours de route
    var versions = ["Msxml2.XMLHTTP.6.0","Msxml2.XMLHTTP.3.0","Msxml2.XMLHTTP","Microsoft.XMLHTTP"];
    // Pour chaque implémentation connue
    for(var i in versions) {
      // On essaye d'instancier et on retourne l'objet en cas de succès
      try {
        return xhr = new ActiveXObject(versions[i]);
      } catch (e) {
        // Gestion de l'erreur
      }
     }
  }
  // Le navigateur est trop vieux ou ne supporte pas l'objet XHR
  else {
    alert("Votre navigateur ne supporte pas l'objet XMLHttpRequest");
    return xhr = false;
  }
} 

Préparer la requête

Une fois l’instance créée, il faut préparer la requête qui sera evnvoyée au serveur. Dans notre cas, c’est très simple : nous interrogeons le serveur via la méthode GET, à l’URL de démonstration ‘http://www.monsite.com’, en mode asynchrone.

xhr.open('GET', 'http://www.monsite.com', true);

Ecouter la requête

Nous allons passer une fonction de callback à l’évènement onreadystatechange, qui va nous permettre de gérer la réponse de notre requête et de réagir correctement en cas d’erreur ou de retour non attendu.

xhr.onreadystatechange = function() {
  if(xhr.readyState === 4 && hxr.status === 200) {
    // Gestion de la réponse
  } else if (xhr.readyState === 4 && xhr.status !== 200) {
    alert("Une erreur est survenue ! \\nCode : \" + xhr.status + \"\\nTexte :\" + xhr.statusText);   
  }
};

Gestion des données

Le contenu est récupéré au format texte pour être affiché directement dans l’élément dont l’ID est “demoFileContent”, et cela grâce à innerHTML.

Pas de traitement des données après réception, donc, puisque dans cet exemple le format utilisé n’est pas sérialisé et que nous nous contentons de récupérer le contenu du fichier.

xhr.onreadystatechange = function() {
  if(xhr.readyState === 4 && hxr.status === 200) {
    document.getElementById('demoFileContent').innerHTML = xhr.responseText;
  }
};

Structurer le code

En l’état, notre code est un peu anarchique. Le minimum minimorum serait de placer notre traitement AJAX dans une fonction. C’est ce que nous faisons :

function loadFile(file) {
  var xhr = getXmlHttpRequest();
  xhr.open('GET', file, true);
  xhr.onreadystatechange = function() {
    if(xhr.readyState === 4 && xhr.status === 200) {
      document.getElementById('demoFileContent').innerHTML = xhr.responseText;
    } else if (xhr.readyState === 4 && xhr.status !== 200) {
      alert("Une erreur est survenue ! \\nCode : \" + xhr.status + \"\\nTexte :\" + xhr.statusText);
    }
};
xhr.send(null);
}

Gérer l’appel vers l’instance XHR

Par facilité dans le cadre de cet exemple, c’est le DOM-1 qui est utilisé pour écouter l’évènement click sur le bouton destiné à lancer la requête :

var fileLoader = document.getElementById('demoFileLoader');
  fileLoader.onclick = function() {
    loadFile(this.value);
  };

Script complet

Enfin, nous plaçons tout notre code dans une IEF pour l’isoler du reste de l’application. Voilà le code complet :

(function() {
  function getXmlHttpRequest() {
    var xhr;
    // Si l'objet XMLHttpRequest est implémenté (navigateurs standards et IE >= 10)
    if(window.XMLHttpRequest) {
      return xhr = new XMLHttpRequest();
    }
    // Si l'objet ActiveXObject est implémenté (IE <= 9)
    else if(window.ActiveXObject) {
      // On sait qu'IE a changé son implémentation de l'objet XHR en cours de route
      var versions = ["Msxml2.XMLHTTP.6.0","Msxml2.XMLHTTP.3.0","Msxml2.XMLHTTP","Microsoft.XMLHTTP"];
      // Pour chaque implémentation connue
      for(var i in versions) {
        // On essaye d'instancier et on retourne l'objet en cas de succès
        try {
          return xhr = new ActiveXObject(versions[i]);
        } catch (e) {
          // Gestion de l'erreur
        }
      }
    }
    // Le navigateur est trop vieux ou ne supporte pas l'objet XHR
    else {
      alert("Votre navigateur ne supporte pas l'objet XMLHttpRequest");
      return xhr = false;
    }
  }     
  function loadFile(file) {
    var xhr = getXmlHttpRequest();
    xhr.open('GET', file, true);
    xhr.onreadystatechange = function() {
      if(xhr.readyState === 4 && xhr.status === 200) {
        var content = xhr.responseText;
        document.getElementById('demoFileContent').innerHTML = content;
      } else if (xhr.readyState === 4 && xhr.status !== 200) {
        alert("Une erreur est survenue ! \\nCode : \" + xhr.status + \"\\nTexte :\" + xhr.statusText);
      }
    };
    xhr.send(null);
  }
  var fileLoader = document.getElementById('demoFileLoader');
  fileLoader.onclick = function() {
    loadFile(this.value);
    return false;
  };
})();

Et la démonstration :

Demo loader XHR

Tout ça pour ça, vous direz-vous ! Oui, nous avons mis en place une requête asynchrone qui sera déclenchée au clic sur un bouton. L’objet XHR récupère directement le contenu d’un fichier texte et l’affiche dans un div. L’important ici est de bien saisir la nature de l’opération qui vient d’être réalisée.

Cela fait, ceux qui souhaitent en apprendre un peu plus sur une utilisation avancée de l’objet XHR peuvent consulter le tutoriel réaliser un système d’auto-complétion en AJAX.

Remarques à propos de l’objet XHR

L’encodage et les requêtes asynchrones

La question de l’encodage est récurrente en développement. Avec l’objet XHR, il faut être particulièrement attentif à ce point dans la mesure où le seul encodage supporté est l’UTF-8. Dès lors, il vous faudra veiller à ce que vos fichiers ainsi que vous sources de données soient bien encodées en UTF-8.

Il existe bien évidemment des fonctions d’encodage/décodage dans la plupart des langages côté serveur, mais c’est une alternative qui ne devra être privilégiée que sur des projets existants et dont la révision n’est pas envisageable. Sur tous vos nouveaux projets il est recommandé d’utiliser directement le bon encodage. Dans la mesure où l’UTF-8 a largement pris le pas sur les autres jeux d’encodage en latin, ce n’est pas un gros effort, il suffit d’y être attentif et d’utiliser l’UTF-8 tout le temps, partout.

Bibliothèque externe, faut-il ou ne faut-il pas ?

Cela dépend très fortement du projet sur lequel vous travaillez ainsi que de vos habitudes de programmation, bien évidemment. La chose à retenir, c’est qu’en interne, les bibliothèques avec lesquelles vous faites éventuellement de l’AJAX utilisent l’objet XHR natif.

L’objet XHR 2

Standardisé et correctement pris en charge par la plupart des navigateurs modernes, y compris IE >= 10, l’objet XHR 2 offre pas mal de propriétés supplémentaires, le support de l’objet FormData et, surtout, permet d’effectuer des requêtes dites “cross domain”, c’est-à-dire des requêtes sur un autre domaine que le script qui lance la requête. Ces nouvelles propriétés, méthodes et implémentations seront abordées dans un autre tutoriel.

A propos de l'auteur

Steve Lebleu

Cross-triathlète, amoureux de nature, de grands espaces et ... d'applications web. Curieux et touche-à-tout, je m'intéresse à tous les aspects du développement d'un projet web. Je suis développeur full stack freelance depuis 2018, principalement sur des piles Javascript.

Ajouter un commentaire