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
avecpython -m http.server --bind 127.0.0.1 5000
(sur le port 5000, enlocalhost
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 styletext-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 :
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
etfetchForLoopAwait
renvoient le même résultat, mais lesfetch
ne sont pas résolues dans le même ordre. Utiliser le comma operator(,)
(MDN) pour instrumenter la fonctionhead
et le prouver en affichant l’ordre de résolution des promesses. - (BONUS) Écrire une nouvelle variante parallèle en utilisant
for ... of
(MDN) oufor await ... of
(MDN) sur une liste de promesses. - (BONUS) (DIFFICILE) Réécrire une fonction équivalente à
Promise.all
en utilisant uniquement des promesses (sansasync
/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/.