LIFWEB TP4 - Programmation asynchrone en JavaScript, Fetch et API

Nous allons réaliser une application qui permet de suivre la météo de ses villes préférées.

Vous pourrez vous appuyer sur les fichiers suivant pour démarrer (clic-droit sauvegarder) :

Nous allons nous appuyer sur deux API :

Exercice 0 : Test des API avec swagger, curl ou httpie

Pour tester les API commençons avec quelques tests simple :

Swagger

Swagger est utilisé pour documenter les API par les fournisseurs (comme vu lors de la démo en cours).

  1. tester le status de l’API : https://api.met.no/weatherapi/ aller dans l’onglet OpenAPI UI, chercher GET /status et cliquer sur “Try it out”
  2. aller dans la partie GET /compact, changer les valeurs pour les latitudes et longitudes d’Oslo: lat: 59.8939243, lon: 10.6203135 et cliquer sur “Try it out”.
  3. aller sur les geoservices de l’IGN et chercher une adresse (la votre par exemple, ou simplement “Lyon”).
  4. récupérer les coordonnées latitude/longitude renvoyées à l’étape précédente, et en utilisant Swagger et la route /search et la requêter de nouveau l’API de met.no.

Depuis le terminal avec curl ou httpie

Répéter l’exercice ci-dessus depuis la ligne de commande avec curl (les commandes à copier dans le terminal seront fournies par Swagger), ou avec httpie (c.f. démo vue en cours).

Exercice 1 : Afficher la météo de coordonnées lat/long

Nous allons démarrer par afficher la météo d’Olso dans le div dédié id=meteo-oslo. Le rendu correspondra à quelque chose proche de ceci, avec la température et l’icone qui changeront en fonction du temps actuel :

Illustration résultat exo 1

Pour cela nous allons effectuer un fetch sur l’API de météo:

  https://api.met.no/weatherapi/locationforecast/2.0/compact?lat=${lat}&lon=${lon}

Utiliser les coordonnées suivantes correspondant à la ville d’Oslo : lat: 59.8939243, lon: 10.6203135

Utiliser les promesses vues en cours pour récupérer les données.

Si la réponse est 200 (response.ok est true), traiter les données de la réponse et modifier les éléments du DOM pertinents (voir le fichier tp4_meteo.html).

Exercice 2 : Afficher la météo d’une ville donnée

Continuons par afficher la météo de Lyon dans le div dédié id=meteo-lyon. Le rendu sera similaire à l’exercice précédent. Cette fois ci nous allons demander à l’API geoservices de l’IGN de nous donner les coordonnées de Lyon.

Récupérer les coordonées latitude/longitude de Lyon.

⚠️ geoservice renvoie d’abord la longitude et ensuite la latitude.

Pour cela nous allons effectuer un fetch sur l’API de geocodage :

   https://data.geopf.fr/geocodage/search?&q=AdresseOUville

Nous allons préférer utiliser async et await à un chainage de .then() voir la documentation de fetch

Si la réponse est 200 (response.ok est true), traiter les données de la réponse. Afficher alors Lyon et les valeurs de latitude et de longitude dans le span “coord”.

Requêter ensuite l’API de météo comme dans l’exercice précédent : https://api.met.no/weatherapi/locationforecast/2.0/compact?lat=${lat}&lon=${lon}

Illustration résultat exo 2

Exercice 3 : Un tableau de bord de ses villes préférées

On va maintenant charger un tableau avec 5 villes et afficher la météo de chacune dans un div dédié:

  const villesFavorites = ["Paris", "Marseille", "Toulouse", "Villeurbanne", "Lille"];

Illustration résultat exo 3

Utiliser un template pour générer la fiche météo de chaque ville.

Le template reprendra le code de l’exercice précédent, mais sera enveloppé dans une balise dédiée:

      <template id="meteo-card-template">
        ...code de présentation de la météo d'une ville...  
      </template>

Un template n’est pas créé, mais cloné :

    const template = document.querySelector("#meteo-card-template");

    /* Pour chaque ville :*/ 
    const $meteoVilleRoot = template.content.cloneNode(true);
    /* traitement sur pour remplir $divMeteoVille avec le contenu de l'API*/
    $parent.append($meteoVilleRoot);

⚠️ Dans l’exemple $divMeteoVilleest un HTMLTemplateElement dont l’attribut HTMLTemplateElement.content (MDN) est un DocumentFragment (MDN) dont seuls les enfants sont ajoutés au DOM.

Exercice 4 : Respecter les règles des fournisseurs de service

Jusqu’ici l’exercice était guidé et le nombre de requêtes effectuées limité. Toutefois, la mise à disposition d’une API gratuite est coûteuse pour les fournisseurs et ces derniers vont contrôler de diverses manières l’usage et essayer de prévenir les abus. Même si ce n’était pas le cas, il est préférable de ne pas consommer plus de ressources que nécessaire. Avant d’utiliser une API, il est donc important d’en connaitre les règles et le fonctionnement.

  1. Lire les Terms of services de met.no, oui c’est ennuyeux, mais ça contient les règles d’utilisation…
  2. Les terms of services expliquent aussi comment le traffic est géré, ils contiennent donc des informations techniques à connaître.
  3. La page Getting Started de met.no permet de prendre en main l’API.

Du côté de l’API de l’IGN :

Exercice 5 : Auto-complétion

Nous allons maintenant permettre aux utilisateurs de choisir la ville de leur choix, pour cela nous allons charger avoir un champ texte avec de l’auto-complétion.

Les champs textes permettent l’auto-complétion “native” en s’appuyant sur l’élément HTML datalist.

Vous pouvez partir du code html suivant :

    <h2>5. Météo au choix</h2>
    <div id="meteo-autocomplete">
      <label for="choix-ville">Choisir une ville:</label>
      <input list="liste-villes" id="choix-ville" name="choix-ville" />
      <datalist id="liste-villes">
        <!-- Options à remplir en javascript -->
      </datalist>
    </div>

Pour gérer cette auto-complétion nous allons charger un fichier au format CSV qui contient plus de 100 villes (une par ligne).

Faire un fetch sur le fichier liste-villes

Le contenu du CSV pourra être lu en utilisant .text() sur la réponse du fetch. Pour transformer ce résultat en un tableau, vous pouvez utiliser .split(_ELEMENT DE SEPARATION_) pour transformer un string en Array.

Rajouter dynamiquement (en javascript) à l’élément datalist la liste des villes de content/liste_villes.csv.

Quand une ville est sélectionnée (événement change) afficher sa météo, vous pouvez appeler la fonction réalisée dans l’exercice 2.

Exercice 6 : Gérer les erreurs d’API et autres cas limites

Nous allons maintenant traiter un volume de villes à afficher plus grand. C’est le fichier .csv (d’une colonne) qui contient une liste de villes dont quelques erreurs. content/liste_villes.csv

Gérer les erreurs

  • Si l’API de geocodage ne connait pas la ville, gérer un affichage qui le signale de manière propre, par exemple en mettant inconnu à la place des coordonnées lat/lon.
  • Si l’API de geocodage a été trop été trop sollicitée, vous serez limité, l’API renverra alors un code HTTP 429, il faudra attendre 5 secondes, avant de refaire une requête.
  • Si l’API de met.no a été trop sollicitée, vous serez d’abord limité (throttled en anglais), l’API renverra aussi un code HTTP 429. L’API peut aussi vous bannir en cas d’abus. En cas d’erreur 429, afficher un message d’erreur qui signale le problème et invite à recharger la page plus tard.
  • Si l’API de met.no a évolué, elle renverra un code 203 (deprecated product).

Alléger le volume de requêtes

Pour ne pas surcharger l’API de géocodage basculer d’une requête GET pour chaque ville, à une requête POST sur la route https://data.geopf.fr/geocodage/search/csv

Consuler la documentation et tenter une requête via Swagger.

La requête n’aura pas de paramètres spécifique, et aura un body composé de :

  1. content/liste_villes.csv comme data
  2. city comme valeur de columns

Cela correspond à la commande curl suivante :

curl -X 'POST' \
  'https://data.geopf.fr/geocodage/search/csv' \
  -H 'accept: text/csv' \
  -H 'Content-Type: multipart/form-data' \
  -F 'data=@french-cities-list.csv;type=text/csv' \
  -F 'columns=city'

Il faut traduire cette requête en un fetch(). Pour se faire, on va récupérer le fichier, lire son contenu, et le renvoyer à l’API :

  1. créer une fonction qui va charger le fichier csv

  2. le lire sous forme de blob, si mareponse est le résultat de votre fetch alors await mareponse.blob() vous renverra le contenu du CSV.

  3. créer un fichier (chargé en mémoire), il prendra 3 arguments
    • un tableau contenant votre blob
    • un nom de fichier
    • un MIME type { type: "text/csv" }
  4. Préparer un objet FormData voir la documentation qui va contenir le body de la requête
    • columns aura pour valeur city
    • data aura pour valeur le fichier créé ci-dessus
  5. Préparer les options de la requête
    • ce sera une requête de méthode POST
    • dont le body est le form créé à l’étape d’avant
  6. Enfin requêter l’API de géocodage sur la route search/csv/

  7. Traiter les erreurs potentielles
    • si la réponse est n’est ok, on lit le contenu de la réponse (JSON.parse(texte de reponse))
    • ou soulève une erreur throw new Error(_texte décrivant l'erreur_);
  8. Traiter la réponse (qui sera normalement un fichier csv “augmenté” avec les latitude et longitude).

  9. Transformer le csv en un tableau JavaScript plus facile à manipuler. Utiliser la fonction csvToArray fournit ici

  10. Pour chaque ville du Tableau requêter l’API de météo, vous avez déjà une fonction qui permet de faire cela.

  11. Traiter l’erreur 429 qui correspond au serveur indiquant une surcharge de requêtes.
    • si suite au fetch, _votre_reponse_.status est 429 alors
    • récupérer la propriété Retry-After de vos headers de réponse avec _votre_reponse_.headers.get(_propriete_qui_vous_interesse_)
    • déclencher une erreur avec throw new Error(_message de l'erreur_);

Répéter les requêtes

  • Si votre requête n’abouti pas relancez là après une seconde, arrêter après 3 échecs.

Exercice 7 (BONUS) : Style

Utiliser la librairie CSS https://bulma.io/ pour vous appuyer sur des styles par défaut CSS. Affecter des classes CSS aux éléments de la page HTML et obtenir un rendu plus proche des normes contemporaines.