Une autre tâche courante dans les sites et applications web modernes est de récupérer des données à partir du serveur pour mettre à jour des sections de la page web sans la recharger entièrement. Ce qui pourrait paraître comme un petit détail, a en vérité eu un impact énorme sur les performances et le comportement des sites. Dans cet article, nous allons expliquer le concept et les technologies qui rendent cela possible, tels que XMLHttpRequest et l'API Fetch.

Prérequis: Notions de base de JavaScript (voir premiers pas, les briques JavaScript, les objets JavaScript), les notions de bases des APIs côté client
Objectif: Apprendre à récupérer des données du serveur web et les utiliser pour mettre à jour le contenu de la page.

Quel est le problème?

À la base, le chargement d'une page web est simple — vous envoyez une requête à un serveur et, tant qu'il n'y a pas eu de problème, les ressources de la page web sont téléchargées et affichées sur votre ordinateur.

A basic representation of a web site architecture

Le problème avec ce modèle c'est qu'à chaque fois que vous voulez mettre à jour une partie de la page, par exemple pour afficher la page suivante d'une liste de produits, vous devez recharger toute la page. Ce surcoût est extrêmement inutile et résulte en une mauvaise expérience utilisateur, particulièrement pour les pages qui sont très larges et très complexes.

L'arrivée d'Ajax

Cela a conduit à la création de technologies qui permettent aux pages web de récupérer de petites portions de données (comme du HTML, XML, JSON, ou texte brut) et de les afficher, uniquement lorsque c'est nécessaire. Cela aide à résoudre le problème décrit ci-dessus.

Nous avons pour cela l'API XMLHttpRequest à notre disposition ou — plus récemment — l'API Fetch. Elles permettent aux pages web de réaliser des requêtes HTTP pour récupérer des ressources spécifiques disponibles sur un serveur, et de formatter les données retournées selon les besoins avant l'affichage.

Note: Dans les premiers temps, cette technique était appelé "Asynchronous JavaScript and XML" (JavaScript asychrone et XML), dit AJAX, parce qu'elle utilisait XMLHttpRequest pour requêter des données XML. De nos jours, c'est rarement le cas; la plupart du temps, on utilise XMLHttpRequest ou Fetch pour requêter des données JSON. Quoi qu'il en soit, le procédé reste le même et le terme "Ajax" est resté pour décrire cette technique.

A simple modern architecture for web sites

Le modèle Ajax implique une API web comme proxy pour requêter les données plus intelligemment que simpliment rafraichir la page à chaque fois. Voyons plutôt l'impact que ça a:

  1. Allez sur un site riche riche en information de votre choix, comme Amazon, YouTube, CNN...
  2. Cherchez quelque chose dans la barre de rechrche, comme un nouveau produit. Le contenu principal va changer, mais la plupart de ce qui l'entoure reste statique, comme l'entête, le pied de page, le menu de navigation, etc.

C'est une bonne chose puisque:

  • La mise à jour de la page est beaucoup plus rapide et vous n'avez pas attendre que la page se rafraichisse, si bien que le site paraît être plus rapide et plus réactif.
  • Moins de données doivent être téléchargées pour mettre à jour la page, et donc moins de bande passante est utilisée. Cela ne fait peut-être pas une grande différence sur un ordinateur de bureau, mais cela peut devenir un problème majeur sur mobile ou dans les pays en développement, qui n'ont pas un service Internet ultra-rapide de partout.

Notez que pour accélerer les choses encore plus, certains sites stockent les ressources et données côté client lors de sa première visite, si bien que les visites suivantes, les fichiers locaux sont utilisés et non re-téléchargés du serveur. Le contenu n'est rechargé que lorsqu'il a été mis à jour sur le serveur.

A basic web app data flow architecture

Une requête Ajax basique

Voyons maintenant comment ces requêtes sont gérées, en utilisant soit XMLHttpRequest soit Fetch. Pour ces exemples, nous allons requêter les données de différents fichiers texte, et les utiliserons pour remplir une zone de contenu.

Ces fichiers agiront comme une fausse base de données; dans une vraie application, il est plus probable que vous utiliseriez un langage côté serveur comme PHP, Python, ou Node pour récupérer les données à partir d'une véritable base de données. En revanche, nous voulons ici garder les choses simples, et nous allons donc nous concentrer uniquement sur le côté client.

XMLHttpRequest

XMLHttpRequest (qui est fréquemment abrégé XHR) est une technologie assez vieille maintenant — elle a été inventée par Microsoft dans les années 90, et a été standardisée dans les autres navigateurs il y a longtemps.

  1. Pour commencer cet exemple, faites une copie locale de ajax-start.html et des quatre fichiers texte — verse1.txt, verse2.txt, verse3.txt, et verse4.txt — dans un nouveau répertoire sur votre ordinateur. Dans cet exemple, nous allons charger le verset d'un poème (que vous pourriez bien reconnaître), quand il est sélectionné dans le menu déroulant, en utilisant XHR.

  2. À l'intérieur de l'élément <script>, ajoutez le code qui suit. Il stocke une référence aux éléments <select> et <pre> dans des variables et définit un gestionnaire d'événement onchange, pour que, quand la valeur du menu déroulant est changée, la valeur sélectionnée est passée comme paramètre à la fonction  updateDisplay().

    var verseChoose = document.querySelector('select');
    var poemDisplay = document.querySelector('pre');
    
    verseChoose.onchange = function() {
      var verse = verseChoose.value;
      updateDisplay(verse);
    };
  3. Définissons maintenant la fonction updateDisplay(). Tout d'abord, mettez ce qui suit au bas de votre JavaScript — c'est la structure vide de la fonction:

    function updateDisplay(verse) {
    
    };
  4. Nous allons commencer notre fonction en construisant une URL relative qui pointe vers le fichier texte que nous voulons charger, nous en aurons besoin plus tard. La valeur de l'élément <select> à tout instant est la même que l'élément <option> sélectionné (c'est à dire le texte de l'élément sélectionné, ou son attribut value s'il est spécifié) — par exemple "Verse 1". Le fichier correspondant est "verse1.txt" et il est situé dans le même répertoire que le fichier HTML, le nom du fichier seul suffira donc.

    Les serveurs web sont généralement sensibles à la casse, le nom de fichier n'a pas d'espace et a une extension de fichier. Pour convertir "Verse 1" en "verse1.txt" nous allons convertir le "V" en minuscles avec toLowerCase(), supprimer l'espace avec replace() et ajouter ".txt" à la fin avec une simple concaténation de chaîne. Ajoutez les lignes suivantes à l'intérieur de la fonction updateDisplay():

    verse = verse.replace(" ", "");
    verse = verse.toLowerCase();
    var url = verse + '.txt';
  5. Pour commencer à créer une requête XHR, vous allez devoir créer un nouvel objet avec le constructeur XMLHttpRequest(). Vous pouvez appeler cet objet comme vous le voulez, mais nous l'appellerons request pour plus de clarté. Ajoutez ce qui suit à vos lignes précédentes:

    var request = new XMLHttpRequest();
  6. Ensuite, vous allez devoir utiliser la méthode open() pour spécifier la méthode HTTP et l'URL à utiliser pour récupérer la ressource. Nous allons ici utiliser la méthode GET et passer notre variable url pour URL. Ajoutez ceci à la suite de la ligne précédente:

    request.open('GET', url);
  7. Nous allons définir le type de réponse que nous attendons — définit par la propriété responseType de la requête — comme text. Ce n'est pas strictement nécessaire ici — XHR retourne du texte par défaut — mais c'est une bonne idée d'en prendre l'habitude pour les cas où vous aurez besoin de définir un type différent. Ajoutez ceci à la suite:

    request.responseType = 'text';
  8. Récupérer une ressource sur le réseau est une opération asynchrone, ce qui signifie que vous devez attendre que cette opération se termine (par exemple, que la ressource soit renvoyée) avant de pouvoir récupérer la réponse — sans quoi une erreur est levée. XHR permet d'exécuter du code lorsque la réponse est reçue grâce au gestionnaire d'événement onload — quand l'événement load est déclenché. Une fois que la réponse a été reçue, alors la réponse est accessible via la propriété response de l'objet XHR utilisé.

    Ajoutez le bloc de code qui suit toujours au bas de la fonction updateDisplay(). Vous verrez qu'à l'intérieur du gestionnaire d'événément onload, nous assignons la propriété textContent de poemDisplay (l'élément <pre>) à la valeur de la propriété request.response.

    request.onload = function() {
      poemDisplay.textContent = request.response;
    };
  9. Les étapes précédentes nous ont permis de configurer la requête XHR, mais celle-ci n'est exécutée que lorsqu'on le demande explicitement. Pour ce faire, il faut appeler la méthode send(). Ajoutez la ligne suivante à la suite des lignes précédentes:

    request.send();

    Voyez la section Servir votre exemple depuis un serveur pour pouvoir tester.

  10. Un dernier problème avec cet exemple est qu'il ne montre rien au chargement de la page (mais uniquement à la sélection d'un verset). Pour corriger cela, ajoutez ce qui suit au bas de votre code (juste au-dessus de la balise fermante </script>), pour charger le verset 1 par défaut, et s'assurer que l'élément <select> montre toujours la bonne valeur:

    updateDisplay('Verse 1');
    verseChoose.value = 'Verse 1';

Servir votre exemple depuis un serveur

Certains navigateurs (dont Chrome) n'exécuteront pas de requêtes XHR si vous exécutez votre script simplement à partir d'un fichier local. Cela est dû à des restrictions de sécurité (pour plus d'infos sur la sécurité web, consultez l'article La sécurité d'un site web).

Pour règler ce problème, vous devez tester l'exemple à travers un serveur web local. Pour savoir comment procéder, lisez Comment configurer un serveur de test local?

Fetch

L'API Fetch est une solution moderne qui vient remplacer XHR — elle a été introduite récemment dans les navigateurs pour rendre les requêtes HTTP asynchrones plus simples en JavaScript, à la fois pour les développeurs et pour les autres APIs qui utilisent cette technologie.

Voyons comment convertir le dernier exemple, en remplaçant XHR par Fetch.

  1. Faites une copie du répertoire de votre dernier exemple. (Ou si vous ne l'avez pas fait, créez un nouveau répertoire et copiez le fichier xhr-basic.html et les quatre fichiers texte — verse1.txt, verse2.txt, verse3.txt, and verse4.txt — à l'intérieur).

  2. À l'intérieur de la fonction updateDisplay(), vous avez le code XHR suivant:

    var request = new XMLHttpRequest();
    request.open('GET', url);
    request.responseType = 'text';
    
    request.onload = function() {
      poemDisplay.textContent = request.response;
    };
    
    request.send();
  3. Remplacez-le avec ce qui suit:

    fetch(url).then(function(response) {
      response.text().then(function(text) {
        poemDisplay.textContent = text;
      });
    });
  4. Chargez l'exemple dans votre navigateur (en l'exécutant à travers un serveur web) et il devrait produire le même résultat que la version XHR  — pourvu que vous utilisiez un navigateur moderne.

Que se passe-t-il dans le code Fetch?

Tout d'abord, nous invoquons la méthode fetch(), en lui passant l'URL de la ressource que nous voulons récupérer. C'est la version moderne équivalente à request.open() de XHR, et n'avez pas à appeler .send() — la requête est exécutée directemment.

Ensuite, la méthode .then() est chaînée à la suite de fetch() — cette méthode fait partie des Promesses, une fonctionnalité JavaScript moderne qui permet d'effectuer des opérations asynchrones. fetch() retourne une promesse, qui est résolue lorsque la réponse est reçue du serveur — et nous utilisons .then() pour exécuter du code à ce moment là. C'est l'équivalent du gestionnaire d'événément onload dans la version XHR.

La fonction définie dans le .then() reçoit la réponse du serveur comme paramètre, une fois que la promesse retournée par fetch() est résolue. À l'intérieur de cette fonction, nous utilisons la méthode text() pour récupérer le contenu de la réponse en texte brut. C'est l'équivalent de request.responseType = 'text' dans la version XHR.

Vous verrez que text() retourne également une promesse, nous y chaînons donc un nouveau .then(), à l'intérieur de quoi nous définissons une fonction. Cette dernière récupère quant à elle le texte brut que la promesse précédente résout.

Enfin, dans le corps de la fonction, nous faisons la même chose que nous faisions dans la version XHR — définir le contenu texte de l'élément <pre> au texte récupéré.

À propos des promesses

Les promesses peuvent être un peu déroutantes au premier abord, ne vous en souciez pas trop pour l'instant. Vous vous y ferez après un certain temps, d'autant plus après en avoir appris plus sur les APIs JavaScript modernes — la plupart des APIs récentes utilisent beaucoup les promesses

Regardons à nouveau la structure de la promesse pour voir si nous pouvons en donner plus de sens.

Promesse 1

fetch(url).then(function(response) {
  //...
});

Si l'on traduit en bon français les instructions JavaScript, on pourrait dire

  • fetch(url): récupérer la ressource située à l'adresse url
  • .then(function() { ... }): quand c'est fait, exécuter la fonction spécifiée

On dit qu'une promesse est "résolue" (resolved) lorsque l'opération spécifiée à un moment donné est terminée. En l'occurence, l'opération spécifiée est de récupérer une ressource à une URL donnée (en utilisant une requête HTTP) et de retourner la réponse reçue du serveur.

La fonction passée à then() n'est pas exécutée immédiatement — à la place, elle est exécutée à un moment dans le futur, dès que la promesse est résolue (c'est à dire qu'elle a retourné la réponse du serveur).

Notez que vous pouvez également choisir de stocker votre promesse dans une variable, et de chaîner le .then() sur cette variable. L'exemple suivant fait toujours la même chose que le précédent:

var myFetch = fetch(url);

myFetch.then(function(response) {
  //...
});

Parce que la méthode fetch() retourne une promesse qui résout une réponse HTTP, la fonction définie à l'intérieur du .then() reçoit la réponse en tant que paramètre. Vous pouvez appeler le paramètre comme vous souhaitez — l'exemple ci-dessous fait toujours la même chose:

fetch(url).then(function(dogBiscuits) {
  //...
});

Mais il est plus logique de donner un nom de paramètre qui décrit son contenu!

Promesse 2

Voyons maintenant la fonction appelé dans .then():

function(response) {
  response.text().then(function(text) {
    poemDisplay.textContent = text;
  });
}

L'objet response a une méthode text(), qui convertit les données brutes contenues dans la réponse en texte brut — c'est le format que nous voulons. Cette méthode retourne également une promesse, qui se résout lorsque la réponse est convertie en texte, nous utilisons donc un deuxième .then() pour cette deuxième promesse.

À l'intérieur de ce dernier .then(), nous définissons une nouvelle fonction, qui décide de ce que nous faisons avec le texte récupéré. Nous nous contentons de définir la propriété textContent de l'élément <pre> à la valeur du texte.

Chaîner les then()

Notez que le résultat de la fonction appelée par le .then() est également retourné par ce dernier, nous pouvons donc mettre les .then() bout à bout, en passant le résultat du bloc précédent au prochain.

Ainsi, le bloc de code suivant fait la même chose que notre exemple original, mais écrit dans un style différent:

fetch(url).then(function(response) {
  return response.text()
}).then(function(text) {
  poemDisplay.textContent = text;
});

Beaucoup de développeurs préfèrent ce style, plus "plat": il évite de définir des fonctions à l'intérieur de fonctions, et est plus facile à lire lorsqu'il y a beaucoup de promesses qui s'enchainent. La seule différence ici est que nous avons une instruction return pour retourner response.text(), et ce résultat est passé au prochain .then().

Quel mécanisme devriez-vous utiliser?

Cela dépend du projet sur lequel vous travaillez. XHR existe depuis longtemps maintenant et bénéficie d'un très bon support sur les différents navigateurs. Fetch et les promesses, en revanche, sont un ajout plus récent à la plateforme web, bien qu'ils soient pris en charge par la plupart des navigateurs, Internet Explorer et Safari font exception.

Si vous voulez un support des anciens navigateurs, alors XHR est probablement la solution préférable. En revanche, si vous travaillez sur un projet plus progressif, et que vous n'êtes pas tant préoccupé par les anciens navigateurs, alors Fetch peut être un bon choix.

Vous devriez apprendre les deux alternatives — Fetch deviendra plus populaire au fur et à mesure que l'utilisation d'Internet Explorer diminue (IE n'est plus développé, en faveur du nouveau navigateur de Microsoft, Edge), mais vous allez avoir besoin de XHR pendant un moment encore.

Un exemple plus complexe

Pour clore l'article, nous allons regarder un exemple un peu plus complexe, qui montre des utilisations plus intéressantes de Fetch. Nous avons créé un site d'exemple appelé The Can Store (le magasin de conserves) — c'est un supermarché fictif qui ne vend que des boites de conserves. Vous pouvez trouver cet exemple en direct sur GitHub, et voir le code source.

A fake ecommerce site showing search options in the left hand column, and product search results in the right hand column.

Par défaut, le site affiche tous les produits; mais vous pouvez utiliser le formulaire dans la colonne de gauche pour les filtrer par catégorie, ou chercher un terme, ou les deux.

Il y a du code plutôt complexe pour traiter le filtrage des produits par catégorie et par terme de recherche, manipulant les chaînes de caractères pour afficher les données correctement dans l'interface utilisateur, etc. Nous n'allons pas en discuter dans cet article, mais vous pouvez trouver des commentaires très complets dans le code (voir can-script.js). Nous allons expliquer le code Fetch.

Premier Fetch

Le premier bloc qui utilise Fetch se trouve au début du JavaScript:

fetch('products.json').then(function(response) {
  if(response.ok) {
    response.json().then(function(json) {
      products = json;
      initialize();
    });
  } else {
    console.log('Network request for products.json failed with response ' + response.status + ': ' + response.statusText);
  }
});

Cela ressemble à ce que vous avons vu précédemment, sauf que la deuxième promesse est à l'intérieur d'une condition. Cette condition vérifie si la réponse retournée est un succès ou non — la propriété response.ok contient un booléen qui vaut true si le statut de la réponse était OK (statut HTTP 200, "OK"), ou false sinon.

Si la réponse était un succès, nous déclenchons la deuxième promesse — cette fois-ci en utilisant json() et non text(), puisque nous voulons récupérer la réponse sous forme de données structurées JSON et non de texte brut.

Si la réponse n'est pas un succès, nous affichons une erreur dans la console indiquant que la requête réseau a échoué, avec le statut et le message obtenus (contenus dans les propriétés response.status et response.statusText respectivement). Bien sûr, un véritable site web traiterait cette erreur plus gracieusement, en affichant un message à l'écran en offrant peut-être des options pour remédier à la situation.

Vous pouvez tester le cas d'échec vous-même:

  1. Faites un copie locale des fichiers d'exemple (téléchargez et dézippez le fichier ZIP can-store)
  2. Éxecutez le code via un serveur web (comme vu précédemment dans Servir votre exemple depuis un serveur)
  3. Modifiez le chemin du fichier qu'on veut récupérer, mettez un nom de fichier qui n'existe pas, comme 'produc.json'.
  4. Maintenant, chargez le fichier index dans votre navigateur (via localhost:8000) et regardez dans la console de développement. Vous verrez un message parmi les lignes "Network request for products.json failed with response 404: File not found" (la requête réseau pour products.json a échoué avec la réponse 404: Fichier non trouvé)

Deuxième Fetch

Le deuxième bloc Fetch se trouve dans la fonction fetchBlob():

fetch(url).then(function(response) {
  if(response.ok) {
    response.blob().then(function(blob) {
      objectURL = URL.createObjectURL(blob);
      showProduct(objectURL, product);
    });
  } else {
    console.log('Network request for "' + product.name + '" image failed with response ' + response.status + ': ' + response.statusText);
  }
});

Cela fonctionne à peu près de la même manière que le précédent, sauf qu'au lieu d'utiliser json(), on utilise blob() — en l'occurence, nous voulons récupérer la réponse sous la forme d'un fichier image, et le format de données que nous utilisons est Blob — ce terme est une abbréviation de "Binary Large Object" (large objet binaire), et peut être utilisé pour représenter de gros objets fichier — tels que des fichiers images ou vidéo.

Une fois que nous avons reçu notre blob avec succès, nous créons un objet URL, en utilisant createObjectURL(). Cela renvoie une URL interne temporaire qui pointe vers un blob en mémoire dans le navigateur. Cet objet n'est pas très lisible, mais vous pouvez voir à quoi il ressemble en ouvrant l'application Can Store, Ctrl + Clic droit sur l'image, et sélectionner l'option "Afficher l'image" (peut légèrement varier selon le navigateur que vous utilisez). L'URL créée sera visible à l'intérieur de la barre d'adresse, et devrait ressembler à quelque chose comme ça:

blob:http://localhost:7800/9b75250e-5279-e249-884f-d03eb1fd84f4

Challenge: Une version XHR de Can Store

Nous aimerions que vous essayiez de convertir la version Fetch de l'application en une version XHR, comme exercice pratique. Faites une copie du fichier ZIP, et essayiez de modifier le JavaScript en conséquence.

Quelques conseils utiles:

  • Vous pourriez trouver la référence XMLHttpRequest utile.
  • Vous allez devoir utiliser le même modèle que vous avez vu plus tôt dans l'exemple XHR-basic.html.
  • Vous devrez ajouter la gestion des erreurs que nous vous avons montré dans la version Fetch de Can Store:
    • La réponse se situe dans request.response une fois que l'événement load a été déclenché, et non dans une promesse.
    • Le meilleur équivalent à response.ok en XHR est de vérifier si request.status vaut 200 ou si request.readyState est égal à 4.
    • Les propriétés permettant d'obtenir le stauts et le message en cas d'erreur sont toujours status et statusText mais elles se situent sur l'objet request (XHR) et non sur l'objet response.

Note: Si vous avez des difficultés à le faire, vous pouvez comparer votre code à la version finale sur GitHub (voir le code source, ou voir en direct).

Sommaire

Voilà qui clôt notre article sur la récupération de données à partir du serveur. À ce stade, vous devriez savoir comment travailler avec XHR et Fetch.

Voir aussi

Il y a beaucoup de sujets abordés dans cet article, dont nous n'avons qu'égratigné la surface. Pour plus de détails sur ces sujets, essayez les articles suivants:

Étiquettes et contributeurs liés au document

Contributeurs à cette page : BigBigDoudou, a-mt
Dernière mise à jour par : BigBigDoudou,