Asynchrone et réseau
TypeScript 101, Télécom Paris
Trois questions sur la séance 5, tirées des QCM du cours.
Que se passe-t-il ?
nom n’existe pasJOEundefinedB. JSON.parse renvoie any : plus rien n’est vérifié.
Quel est le type de a ?
anystring | undefinedTstring[]B. TypeScript déduit T = string ; le tableau pourrait être vide.
Qu’affiche ce code ?
A B C D · B. A C D · C. A C · D. A DB. throw interrompt le try ; finally s’exécute dans tous les cas.
Promise et prédire un ordre d’exécutionasync / await et traiter les erreursfetch et typer le résultatEn C, read() bloque votre programme, et le système donne la main à un autre processus. JavaScript ne fonctionne pas ainsi.
Dans un navigateur, tout votre code s’exécute sur un seul fil : celui qui redessine la page et réagit aux clics. Si une fonction bloque deux secondes :
Une requête réseau met 200 ms, parfois 5 secondes. Bloquer n’est pas une option.
PromiseUn objet qui représente un résultat de type T qui arrivera plus tard, ou qui n’arrivera jamais.
| État | Anglais | Signification |
|---|---|---|
| en attente | pending | l’opération est en cours |
| tenue | fulfilled | réussite, la promesse porte une valeur |
| rompue | rejected | échec, la promesse porte une erreur |
Une promesse part de pending et change d’état une seule fois, définitivement. Le type entre chevrons est celui de la valeur finale : Promise<string> signifie « une chaîne, plus tard ».
await : attendre sans bloquerDire qu’await « met en pause » est exact, mais il faut préciser quoi.
await suspend uniquement la fonction qui l’exécute. Le fil n’est pas bloqué : il part faire autre chose et reviendra terminer la fonction.
C’est toute la différence avec un sleep() en C, qui bloque tout.
async : la contrepartieawait ne s’écrit que dans une fonction async, ou à la racine d’un module ES, ce qui inclut le frontmatter d’un fichier Astro.
Une fonction async retourne toujours une promesse, que vous le vouliez ou non.
Oubliez le await à l’appel, et vous affichez Promise { <pending> }.
async function fetchData(): Promise<string> {
console.log("[2] début de fetchData");
await delay(2000);
console.log("[4] les 2 secondes sont écoulées");
return "Some data";
}
console.log("[1] avant l'appel");
fetchData().then((data) => console.log("[5] reçu :", data));
console.log("[3] après l'appel");Prédisez la sortie avant de tourner la page.
[1] avant l'appel
[2] début de fetchData
[3] après l'appel
... deux secondes s'écoulent ...
[4] les 2 secondes sont écoulées
[5] reçu : Some data
[2] précède [3]await, la fonction rend la main : le fil reste libre[4] et [5] arrivent quand la promesse est tenue[3] s’exécute alors que fetchData n’est pas terminée. Pour attendre réellement, il faut await fetchData(), donc être soi-même dans une async.
Le plus souvent vous consommez des promesses. Il arrive d’en créer une, pour emballer une ancienne API à callback.
resolve(v) fait passer à fulfilled, reject(e) à rejected.
.then() ou await ?Préférez async / await : le code se lit de haut en bas, et un seul try / catch gère les erreurs synchrones et asynchrones.
await, pas de catchLe bloc try se termine avant que la promesse ne soit rompue. Le rejet devient une unhandled promise rejection.
Retenez la règle
Pas de await, pas de catch.
Promise.allChaque await attend le précédent, alors que les requêtes sont indépendantes.
Résultats dans l’ordre des promesses fournies. Si une seule est rompue, tout l’ensemble l’est. Sinon, utilisez Promise.allSettled.
fetch ?La fonction standard du navigateur (et de Node.js depuis la version 18) pour envoyer une requête HTTP.
Elle ne retourne pas les données. Elle retourne une Response : un objet qui décrit la réponse HTTP et qui sait, à la demande, lire le corps du message.
Une réponse HTTP n’arrive pas d’un bloc. Le premier await se termine dès les en-têtes reçus ; le corps peut faire plusieurs mégaoctets.
| Méthode | Retourne | Usage |
|---|---|---|
response.json() |
Promise<any> |
corps JSON, le cas courant |
response.text() |
Promise<string> |
HTML, CSV, texte brut |
response.blob() |
Promise<Blob> |
image, fichier binaire |
Le corps ne se lit qu’une seule fois : sinon body stream already read.
fetch ne rejette pas sur un 404L’erreur la plus coûteuse du chapitre
La promesse de fetch est tenue dès que le serveur a répondu, même avec un 404 ou un 500. Du point de vue de fetch, « le serveur a répondu 404 » est un succès de communication.
Elle n’est rompue que si la requête n’a pas pu aboutir : DNS, câble, blocage CORS.
response.ok vaut true si et seulement si le statut est entre 200 et 299.
interface Earthquake {
properties: { time: number; place: string; mag: number };
}
async function getEarthquakes(): Promise<Earthquake[]> {
const response = await fetch(URL_USGS);
if (!response.ok) {
throw new Error(`USGS a répondu ${response.status}`);
}
const json = await response.json();
return json.features;
}On ne décrit que les champs utilisés, pas tout le GeoJSON. Annoter le retour Promise<Earthquake[]> est ce qui donne sa valeur au code.
Le type n’existe qu’à la transpilation
Si l’API change son format, TypeScript ne s’en apercevra pas : votre code plantera à l’exécution.
L’interface documente et protège votre code. Elle ne valide pas les données entrantes : pour cela, il faut un prédicat de type, comme au chapitre 5.1.
| Méthode | Rôle |
|---|---|
document.createElement("ul") |
crée un élément, détaché de la page |
element.textContent = "..." |
définit le texte |
parent.appendChild(enfant) |
insère dans la page |
textContent et non innerHTML : la donnée vient d’une API.
Le triplet schéma + hôte + port. Deux URL ont la même origine si et seulement si les trois sont identiques.
Comparée à https://exemple.fr/a |
Même origine ? | Pourquoi |
|---|---|---|
https://exemple.fr/b/c |
oui | le chemin ne compte pas |
http://exemple.fr/a |
non | schéma différent |
https://api.exemple.fr/a |
non | un sous-domaine est un autre hôte |
https://exemple.fr:8080/a |
non | port différent |
Votre Astro sur localhost:4321 et votre json-server sur localhost:3000 sont deux origines distinctes.
Un navigateur autorise une page à envoyer une requête vers une autre origine, mais lui interdit d’en lire la réponse.
Le scénario qui la justifie : vous êtes connecté à votre banque dans un onglet. Sans cette politique, le JavaScript d’un autre site pourrait faire fetch("https://mabanque.fr/api/comptes"), avec vos cookies de session, et exfiltrer vos relevés.
CORS est le mécanisme standard qui permet à un serveur de lever sélectivement cette restriction, pour les origines de son choix.
Access to fetch at 'https://api.deezer.com/genre/' from origin
'http://localhost:4321' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present.
fetch marche depuis Node.jsstatus, ni corps à inspectercurl n’est pas concernéCORS est une règle appliquée par les navigateurs, pas par les serveurs. curl, Node.js, Python, Postman n’ont aucune notion d’origine.
Conséquence : CORS ne protège pas les données du serveur. Il protège l’utilisateur du navigateur contre les sites qui agiraient en son nom.
Access-Control-Allow-Origin: https://monsite.fr
Access-Control-Allow-Origin: * (API ouverte à tous)
Pour les requêtes non triviales (PUT, DELETE, en-tête Authorization, Content-Type: application/json), le navigateur envoie d’abord une requête OPTIONS de preflight.
Le serveur doit alors répondre en plus avec Access-Control-Allow-Methods et Access-Control-Allow-Headers.
| Situation | Solution |
|---|---|
| L’API est à vous | ajoutez Access-Control-Allow-Origin |
| L’API est publique et documentée | rien à faire |
| L’API refuse les origines externes | requête côté serveur |
Deux fausses solutions
Une extension « CORS unblock » ou Chrome lancé avec --disable-web-security désactivent une protection pour tous les sites que vous visitez : messagerie et banque comprises. Et votre application reste cassée chez les autres.
Bénéfice annexe du proxy : la clé d’API reste sur le serveur.
« Côté serveur », dans Astro, signifie : dans le frontmatter, entre les deux lignes ---.
Votre page ne parle jamais à Deezer : plus de requête entre origines.
Si un script du navigateur doit réutiliser les données (tri, filtrage, bouton) :
On annote délibérément en unknown pour se forcer à vérifier : le prédicat de type du chapitre 5.1.
Parcours essentiel, dans l’ordre
Ensuite