AJAX

Principe

Jusqu'à maintenant...

  • Les applications côté client que nous avons écrites sont intégralement chargées avec la page HTML.

  • Toute communication avec le serveur se fait par rechargement de la page...

  • ... et donc réinitialisation de l'application (ou d'une autre).

Ce serait bien si...

  • Nos applications pouvaient communiquer avec le serveur,

  • sans avoir à se recharger totalement :

    • conservation de l'état de l'application

    • fluidité pour l'utilisateur

  • Mais cela suppose que le code Javascript ne se bloque pas en attendant la réponse du serveur

    → appel asynchrone (événements)

Définition

AJAX signifie

  • Asynchronous

  • Javascript

  • And

  • XML

... mais en pratique, on peut échanger n'importe quoi avec le serveur, pas uniquement du XML.

Fetch

La fonction fetch permet d’aller chercher une ressource sur un serveur de façon asynchrone.

  • Paramètre(s) :

    • une chaîne de caractères (l’URL) ou un objet Request

    • un objet javascript contenant des options pour la requête HTTP comme le verbe (GET par défaut), les en-têtes.

  • Valeur de retour: une promesse qui se résout en objet Response

L'objet Response

Il représente la réponse à une requête HTTP.

  • Propriétés :

    • body : un flux vers le contenu de la ressource récupérée,

    • status : le code statut de la réponse,

    • ok : un booléen qui indique si c’est une réponse considérée comme un succès (standard HTTP : codes 200 - 299).

  • Méthode :

    • json() : retourne une promesse qui se résout en objet javascript, en tableau ou en variable issus de l’analyse de contenu d’une réponse en format JSON.

L’objet Promise

  • Une promesse représente la résolution (ou l’échec) éventuel(le) d’une opération asynchrone et son résultat.

  • C’est le bon moyen pour représenter le résultat d’une opération asynchrone qui peut s’exécuter de façon favorable et retourner ce qu’on attend, ou qui renvoie une erreur en cas d’échec.

  • Elle a trois états possibles :

    • pending (en cours) : quand la promesse n’a pas encore ni échoué ni été réalisée,

    • fulfilled (réalisée) : quand la promesse s’est réalisée avec succès,

    • rejected (rejetée) : quand la promesse a échoué.

La méthode then

Ajoute un gestionnaire de succès et un gestionnaire d’échec à la promesse.

  • Elle prend en paramètres une ou deux fonctions de callback.

  • Le premier paramètre sera la fonction appelée lorsque la promesse sera réalisée.

  • Le second paramètre (optionnel) sera la fonction appelée lorsque la promesse échoue.

  • Ces deux fonctions ne pourront avoir qu’un paramètre : la valeur retournée en cas de succès ou d’échec.

Indication

then se comporte comme un listener qui écoute le changement d’état de la promesse.

//send HTTP request
fetch('json/chapitre1.json')
//then waits for the Response
  .then(
    function(response){//the request succeeds
      console.log('success:' + response.status); },
    function(error){ //network problem
      console.log('fetch fails:');
      console.log(error);
    }
  );

Chaîne de promesses

Les promesses peuvent être enchaînées.

fetch('json/chapitre1.json')
  //then waits for the Response
  .then(
    function(response){return response.json();},
    function(error){console.log(error) ;})
  //then wait for the json parsing
  .then(
    function(data){console.log(data); },
    function(error){console.log(error);}//format error
  );

La méthode catch

  • Elle peut remplacer l'utilisation d'un second paramètre avec la méthode then (plus lisible).

  • Elle peut permettre de "factoriser" la gestion d’erreurs.

//send HTTP request
fetch('json/chapitre1.json')
  .then(function(response){ //the request succeeds
    console.log('success:' + response.status); })
  .catch(function(error){ //network problem
    console.log('fetch fails:');
    console.log(error);
  });

Les méthodes then et catch

  • Elles fonctionnent comme des "transformateurs": elles font passer le résultat de la promesse à travers les callback, et retournent une promesse à leur tour.

  • Il est donc toujours possible d’enchaîner un then après un then.

fetch('toto')
  .then((x) => 42) // "enveloppe" 42 dans une promesse
  .then((y) => console.log(y)); // Affiche 42
  • then avec un seul paramètre ne transforme que les promesses "succès", et laisse passer telles quelles les promesses "erreur".

  • catch fait l’inverse : elle laisse passer les promesses "succès", et transforme les promesses "erreur".

Gestion "factorisée" des erreurs

fetch('json/chapitre1.json')
  //then waits for the Response
  .then(function(response){
    return response.json();
  }})
  //then wait for the json parsing
  .then(function(data){
    //do what you have to do with data
  })
  //captures and displays all errors
  .catch(function(error){
    console.log(error);
  })
  //avoids duplicated code in then () and catch ()
  .finally(function(){
    console.log('Process completed');
  });

Exceptions personnalisées

Il est inutile de parser le contenu d’une réponse "404 Not Found".

fetch('json/chapitre1.json')
  .then(function(response){
    if(response.ok){return response.json();}
    else{throw("Err " + response.status);}
  })
  //then waits for the json parsing
  .then(function(data){
  //do what you have to do with data
  })
  //captures any rejected promise
  .catch(function(error){console.log(error);
  });

Indication

L’exception lancée dans le callback du 1er then sera "convertie" en "promesse échouée", et donc sera propagée jusqu’à l’appel de catch.

AJAX et Sécurité

Imaginez...

  • Vous visitez le site pirate.net.

  • La page de garde contient un script qui effectue une requête sur gmail.

  • Comme vous êtes déjà identifié, gmail renvoie une page HTML contenant la liste de vos messages récents.

  • Le script analyse ces données, les renvoie à pirate.net, et bien sûr n'affiche rien de ce qu'il vient de faire.

  • Les pirates connaissent maintenant une partie de vos contacts, et savent de quoi vous parlez avec eux.

  • Mais pirate.net aurait pu tout aussi bien faire une requête sur gmail qui

    • efface la liste de vos contacts,

    • envoie un mail à votre place,

    • change votre mot de passe...

  • Bien sûr, il pourrait également tenter une connexion sur

    • les principaux réseaux sociaux,

    • les sites de banque,

    • etc...

Règles de sécurité

Pour éviter ce genre d'attaque, fetch possède des limitations :

  • elle ne peut pas accéder aux URLs en file:,

  • le code émis par un serveur ne peut se connecter qu'à ce même serveur (Same Origin Policy),

  • ou à un serveur autorisant explicitement les accès par d'autres scripts que les siens (standard CORS).

CORS en deux mots

  • Lorsqu'un script, provenant d'un serveur srv1, émet une requête AJAX vers un serveur srv2, cette requête contient un en-tête supplémentaire :

    Origin: http://srv1

  • Si le serveur srv2 fait confiance à srv1, il inclut dans sa réponse l'en-tête suivant :

    Access-Control-Allow-Origin: http://srv1

  • ... et le navigateur autorise alors le script à accéder à la réponse.

  • Dans le cas contraire, le navigateur refuse de transmettre la réponse au script. Du point de vue du script, c'est comme si la requête avait échoué.

Remarques

  • Une solution consiste à utiliser un proxy tel que https://crossorigin.me/ .

  • Auparavant, d'autres méthodes ont été proposées pour permettre des accès cross-domain, comme JSONP.

Objets Javascript et JSON

Principe

  • Javascript possède une notion d'objet qui se trouve à mi-chemin entre les objets du Java, les structures du C, et les dictionnaires du Python.

  • Contrairement à Java ou C, ces objets peuvent être créés sans classe/type prédéfini.

const p = {
  "nom": "Doe",
  "prénom": "John",
  "age": 42
};

Indication

Les attributs peuvent contenir n'importe quel type, y compris bien sûr des types complexes comme des tableaux ou d'autres objets.

Utilisation

  • Pour accéder à un attribut d'un objet, on utilise la même notation « pointée » qu'en Java ou en C :

    let n = p.nom ;
    
  • Mais on peut également utiliser la notation « indicée » comme en Python :

    let pr = p['prénom'] ;
    
  • Cette dernière est utile lorsque :

    • le nom de l'attribut comporte des caractères non autorisés, ou

    • le nom de l'attribut est contenu dans une variable.

Remarque

Cela dit, les caractères accentués (comme dans prénom ci-dessus) sont autorisés. On aurait donc, dans ce cas, pu écrire :

let pr = p.prénom ;

Modification

p.prénom = "Jane" ;
p.adresse = "42, Main road" ;
delete p.age ;
  • On peut ajouter de nouveaux attributs (ex: adresse)

  • On peut supprimer les attributs (ex: age).

  • Si on tente d'accéder à un attribut inexistant, on obtient la valeur undefined.

JSON

JSON (Javascript Object Notation) est un sous-ensemble du langage Javascript, utilisé comme format de données sur le Web.

Données supportées par JSON

  • Objet {}

  • Tableaux []

  • Chaînes de caractères

  • Nombres

  • Booléens

  • null

Syntaxe

  • Les chaînes de caractères doivent être entourées par des guillemets doubles (les guillemets simples ne sont pas supportés).

  • Les nom des attributs des objets doivent être entre guillemets doubles.

  • NB: null est supporté, mais pas undefined

  • Des structures complexes peuvent être représentées en JSON : tableaux d'objets, objets contenant d'autres objets...

Format d'échange

  • Étant directement basé sur Javascript, JSON est bien sûr très utilisé dans ce langage,

  • mais il l'est également dans la plupart des autres langages de programmation, où il a largement remplacé XML (plus simple, plus compact).

Utilisation

  • JSON.parse prend une chaîne de caractères JSON et renvoie l'objet correspondant.

  • JSON.stringify prend un objet supporté par JSON et renvoie la chaîne de caractères correspondante.

TP : Livre dont vous êtes le héros

Sujet

  • Récupérez cette archive, qui contient les différents chapitres d'un livre dont vous êtes le héros, sous forme de structures JSON.

  • Hébergez ces fichiers dans votre public_html, avec une application Javascript permettant de parcourir ce livre.

Indication

Pour les liens du livre, il vous est conseillé

  • d'utiliser des liens HTML internes (href="#xyz") qui n'entrainent pas de rechargement de la page, et

  • d'intercepter les changements en vous abonnant à l'événement hashchange de window, et en utilisant window.location pour déterminer le contenu à afficher.

Remarque

Une alternative consisterait à intercepter les clics sur les liens, pour savoir quand charger et afficher les éléments du livre. Cette solution peut sembler plus naturelle pour le programmeur, mais elle crée une expérience utilisateur moins bonne :

  • la "navigation" à l'intérieur du livre n'est pas stockée dans l'historique du navigateur ;

  • par conséquent, le bouton "retour" ne fonctionne pas ;

  • un rechargement de la page redémarre au premier élément ;

  • il n'est pas possible de mettre un signet sur l'endroit où l'on se trouve.

Annexe

ECMAScript 2017: async/await

async function downloadFirstChap(){
  try{
    let response = await fetch('json/chapitre1. json');
    let data = await response.json() ; //do what you have to do with data
  }
  catch(error){
    console.log(error);
  }
}
  • Pour utiliser await, une fonction doit être async

  • await met en pause l’exécution de la fonction async tant que la promesse n’est pas résolue et "retourne" sa valeur en cas de succès.

  • Cela permet d’utiliser un simple try/catch pour gérer les erreurs

async function downloadFirstChap(){
  try{
    let response = await fetch('json/chapitre1. json');
    let data = await response.json(); //do what you have to do with data
  }
  catch(error){
    console.log(error);
  }
}

Avertissement

Si on oublie le await devant une fonction qui retourne une promesse, la variable contient une promesse, pas son résultat quand cette promesse est résolue → source de bug parfois difficile à détecter !