LIFWEB TP4 - Programmation asynchrone en JavaScript

Ce TP utilise l’API fetch du navigateur https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API. Ce TP nécessite un serveur Web de développement, aussi simple soit-il. Vous pouvez utiliser soit :

  • L’extension Live Server
  • Ouvrir un serveur Python avec le module http.server avec python -m http.server --bind 127.0.0.1 5000 (sur le port 5000, en localhost seulement).

On donne la page de départ tp4-async.html.

Exercice 0 (PRÉPARATION) : Galerie de chats

Le CM2 sur le DOM, proposait une démonstration avec des chats. La galerie est générée dynamiquement côté client à partir d’un tableau.

Reprendre la galerie, mais au lieu d’utiliser un tableau statique de félins (cats dans le code) utiliser une requête fetch sur https://api.thecatapi.com/v1/images/search?limit=10 pour obtenir la liste et générer la galerie à partir du json obtenu. On devrait obtenir de nouveaux chats à chaque rechargement.

Réaliser la modification en style Promise.then() et en style async/await.

Exercice 1 : requête GET avec fetch

On utilise l’API fetch pour remplir dynamiquement le contenu d’une page. Quand on clique sur le bouton Go download!, effectuer le traitement suivant :

  • En JS, télécharger all-bookmarks-status.json avec fetch.
  • Lire le contenu du fichier en JSON avec response.json().
  • Pour chacun des liens contenus, ajouter un élément de la forme <li><a href="url_du_lien" target="_blank">titre du lien</a></li> à la liste.
  • Si le lien est mort (attribut status), afficher quand même le lien, mais avec un style text-decoration: line-through pour qu’il soit rendu barré. Voir la classe CSS .offline fournie.
  • Quand on clique sur un lien, demander une confirmation avec confirm() (MDN) avant de quitter la page.
    • Intercepter l’événement par défaut pour cela.
    • Si le lien est mort, on ne fera rien.

Le rendu final, pour ici une liste de quatre liens dont deux morts, pourrait ressembler à celui-ci :

Rendu après remplissage

L’exercice peut être réalisé avec les Promise ou en utilisant async/await, sans promesses explicites. Il est recommandé d’essayer les deux versions.

Exercice 2 : fetch en parallèle ou en série

On donne le code suivant du fichier compare-await-all.js avec différentes façons d’interroger trois sites :

const uris = [
  "https://pokeapi.co/api/v2/",
  "https://the-site-that-do-no-exists/",
  "https://httpbin.org/status/404",
];

const head = (uri) =>
  fetch(uri, { method: "HEAD" })
    .then((response) => response.status)
    .catch((error) => error.message);

(async () => {
  async function fetchPromiseAll(uris) {
    return await Promise.all(uris.map((uri) => head(uri)));
  }

  async function fetchForLoopAwait(uris) {
    const results = [];
    for (const uri of uris) {
      results.push(await head(uri));
    }
    return results;
  }

  async function fetchMapAwait(uris) {
    return uris.map(async (uri) => await head(uri));
  }

  console.log(await fetchPromiseAll(uris));
  console.log(await fetchForLoopAwait(uris));
  console.log(await fetchMapAwait(uris));
})();
  • Pourquoi utilise-t-on la méthode HTTP HEAD ?
  • Que renvoie fetchMapAwait ?
  • Les fonctions fetchPromiseAll et fetchForLoopAwait renvoient le même résultat, mais les fetch ne sont pas résolues dans le même ordre. Utiliser le comma operator (,) (MDN) pour instrumenter la fonction head et le prouver en affichant l’ordre de résolution des promesses.
  • (BONUS) Écrire une nouvelle variante parallèle en utilisant for ... of (MDN) ou for await ... of (MDN) sur une liste de promesses.
  • (BONUS) (DIFFICILE) Réécrire une fonction équivalente à Promise.all en utilisant uniquement des promesses (sans async/await).

Opérateur virgule (,)

La virgule, e1, e2 en JavaScript évalue le premier argument e1, oublie sa valeur, évalue le second argument e2 et renvoie sa valeur. Le premier argument n’a donc d’intérêt que s’il a des effets de bord, par exemple :

// affiche "echo" juste avant l'affectation de 42 à val
const val = (console.log("echo"), 42);
console.assert(val === 42);

Exercice 3 (BONUS) : soumission de formulaire à serveur tiers

Le but de cet exercice est de gérer ce formulaire côté client, c’est-à-dire qu’on ne va pas envoyer une requête HTTP au serveur d’origine quand on clique sur le bouton Envoyer. À la place, on remplace le comportement par défaut des formulaires par une requête POST à un serveur tiers, ici https://httpbin.org/, et utiliser sa réponse HTTP dans la page, sans passer par le serveur d’origine qui a servi la page HTML.

On veut récupérer les contenus saisis par l’utilisateur dans le formulaire. L’API DOM propose une classe FormData (MDN) pour faciliter la gestion des formulaires. C’est ce que fait la fonction formDataToJSON ci-dessous :

function formDataToJSON(formElt) {
  const formData = new FormData(formElt);
  return Object.fromEntries(formData.entries());
}
  • Avec une requête type POST, envoyer le contenu du formulaire à https://httpbin.org/anything, un exemple est donné après.
  • Récupérer la réponse HTTP envoyée par le serveur et vérifier que le contenu est bien identique à celui envoyé.
  • Tester avec la route https://httpbin.org/status/501 du même site. Traiter l’erreur.
fetch("https://httpbin.org/anything", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify(data),
});

Exercice 4 (AU LONG COURS) : Serveur CTF

Passer les deux premiers niveaux du CTF sur https://lifweb.univ-lyon1.fr/.

Logo CTF LIFWEB