Cours 3
Asynchronisme en JavaScript Ajax, JSON, Event loop

Plan du cours

Asynchronisme utilisé pour résoudre des problèmes de blocage.

Plan :

  1. Chargement des pages Web

  2. AJAX : requêtes HTTP (a)synchrones
    1. Présentation, objectifs
    2. Échanges de données au format JSON
    3. Requêtes avec XMLHttpRequest
    4. Asynchrone pour résoudre des blocages
  3. Boucle des évènements

Chargement des pages Web

Chargement des pages Web

  1. Récupération de la page HTML
  2. Lecture du document HTML au fur et à mesure
    1. Nœuds balise, texte :
      rajoutée au DOM au fur et à mesure
    2. Feuille CSS externe :
      chargement de la feuille en parallèle (non bloquant)
    3. Balises images, vidéos :
      le fichier est chargé en parallèle (non bloquant)
    4. JavaScript :
      chargement du fichier JS puis exécution
      Attention : Bloque la construction du DOM et du CSS !

Chargement des scripts

Problèmes de chargement


Erreur en cas d’interaction trop tôt.



Page montrant le chargement progressif.

Chargement des scripts

Solution pour que le DOM soit prêt lors de l’exécution du script :

  • Mettre la balise <script> à la fin :
    • Inconvénient : Ne télécharge pas le script avant d’arriver à la fin du document.

Chargement des scripts

Chargement des scripts

Solution pour que le DOM soit prêt lors de l’exécution du script :

  • Attendre la fin du chargement du DOM (événement DOMContentLoaded) avant de lancer le script :
    • Avantage : balise <script> où l’on veut.
    • Inconvénient : Le téléchargement du script ne se fait pas en parallèle du chargement du DOM.

Chargement des scripts

Rappel : L’événement DOMContentLoaded est lancé quand le document a été chargé et analysé, sans attendre les CSS, images, …

document.addEventListener("DOMContentLoaded", 
  function() {
    // code qui nécessite le chargement complet du DOM 
});

Chargement des scripts

Solution pour que le DOM soit prêt lors de l’exécution du script :

  • <script src="..." defer></script> : (Recommandé)
    Le script est chargé en parallèle de la lecture du DOM et ne sera exécuté qu’une fois le DOM prêt.
    • Avantage : Les scripts sont exécutés dans l’ordre.
    • Inconvénient : Comme ils sont exécutés dans l’ordre, un <script defer> ne peut s’exécuter qu’après le chargement des <script defer> précédents.


Chargement des scripts


Note : Seulement pour les scripts à télécharger (= avec attribut src).

Chargement des scripts

Pour information, autre manière de charger un script :

  • <script src="..." async></script> :
    Le script est téléchargé en parallèle de la lecture du DOM (non bloquant) et exécuté dès qu’il est disponible.


Chargement des scripts


Attention :

  • Le DOM n’est pas forcément prêt.
  • Les scripts ne sont pas forcément exécutés dans l’ordre où ils sont déclarés.

Récapitulatif


Chargement des scripts


On vous conseille généralement de privilégier defer.

Source

AJAX :
Requêtes HTTP (a)synchrones

Utilisation classique d’un serveur

Utilisation classique d’un serveur

Utilisation classique d’un serveur

Utilisation classique d’un serveur

Utilisation plus dynamique

Utilisation plus dynamique

Utilisation plus dynamique

Exemples d’utilisation de AJAX



  1. Autocomplétion des recherches Google

  2. Zoom sur les cartes OpenStreetMap

  3. Défilement infini dans un fil Twitter

Avantages de AJAX

Diminution du temps de latence :

  • pas de page Web à recharger
  • la page reste utilisable pendant une requête AJAX

Ciblage :

  • affecte seulement une partie de la page
    Exemple : la <div> qui accueille les noms de ville
  • moins d’échanges de données

Amélioration de l’expérience utilisateur :

  • Possibilité de réagir en direct aux actions de l’utilisateur

Technologies utilisées

  • JavaScript pour l’objet qui gérera la requête au serveur (objet XMLHttpRequest) ;

  • PHP côté serveur pour communiquer avec la base de données (ou un autre langage côté serveur).

  • JSON comme format de données pour les échanges entre client et serveur.
    JSON = JavaScript Object Notation.

  • L’ensemble de ces technologies est regroupé sous le nom AJAX
    AJAX = Asynchronous JavaScript And XML
    (car le format de données XML était plus utilisé avant)
    AJAJ = Asynchronous JavaScript And JSON

Technologies utilisées

Alt text

Technologies utilisées

Alt text

Technologies utilisées

Alt text

Technologies utilisées

Alt text

Format JSON

Exemple d’un fichier de données au format JSON

Alt text

Traduction JavaScript → JSON

class Point {
   constructor (x,y,couleur,marqueur) {
      this.x = x;
      this.y = y;
      this.couleur = couleur;
      this.marqueur = marqueur;
   }
}
let A = new Point(5,-3,"rouge","croix");
console.log(A);
// → Point { x:5, y:-3, couleur:'rouge', marqueur:'croix' }

JSON.stringify(A);
// → '{"x":5,"y":-3,"couleur":"rouge","marqueur":"croix"}'

Traduction JSON → JavaScript

let text = '{"x":2,"y":5,"couleur":"jaune","marqueur":"rond"}'
let point = JSON.parse(text)
console.log(point);
// Affichage de la sortie DevTools
// → Object { x:2, y:5, couleur:'jaune', marqueur:'rond' }



Remarque : PHP sait aussi lire et écrire le JSON

json_encode($var_php);
json_decode($json_string);

Donc JSON permet à PHP et JS de communiquer !

L’interface XMLHttpRequest

  • Sert à interagir avec un serveur HTTP :
    • envoyer une requête HTTP
    • lire la réponse HTTP
  • Il est instancié ainsi :
    let xhr = new XMLHttpRequest();
    
  • Il dispose de méthodes pour ouvrir une requête, l’envoyer, l’abandonner, connaître l’évolution de son statut, connaître le contenu de la réponse.

Rappel : requête/réponse HTTP

Demander une page Web, c’est envoyer une requête HTTP à un serveur HTTP :

GET /~rletud/index.html HTTP/1.1
Host: webinfo.iutmontp.univ-montp2.fr

Le serveur renvoie alors sa réponse HTTP, qui contient la page Web demandée dans son corps :

HTTP/1.1 200 OK
Content-Length: 65585
Content-Type: text/html
Last-Modified: Wed, 09 Apr 2014 10:48:09 GMT

<!doctype html>
<html>...

Rappel : requête/réponse HTTP

Requête :

  • la méthode de la requête (GET, POST, DELETE, PUT)
  • le chemin de la ressource
  • la version du protocole HTTP
  • le Host, c.-à-d. le nom de domaine du serveur HTTP

Réponse :

  • la version du protocole HTTP
  • l’état (status) de la réponse sous forme numérique et texte :
    (erreur si ≥ 400)
    • 200 OK
    • 304 Not Modified
    • 404 Not Found
    • 500 Internal Server Error

Requêtes POST

Les requêtes HTTP de type POST possèdent un corps de requête en plus de l’en-tête.

Le corps de la requête HTTP sert ici à envoyer les informations.

POST /~rletud/traitePost.php HTTP/1.1
Host: localhost
Content-Length:14
Content-Type:application/x-www-form-urlencoded

prenom=Marc&nom=Assin

Méthodes de XMLHttpRequest

  1. la méthode open (ouvre la requête)

    xhr.open("GET", "http://www.google.fr", true);
    

    Le 3ème argument indique si la requête est asynchrone

  2. la méthode send (envoie la requête avec un corps de requête)

    xhr.send(contenu)
    
    • si la méthode est GET, contenu est null;
    • si la méthode est POST, contenu est
      • soit null
      • soit égal à une chaîne du type
        param1=valeur1&param2=valeur2&…

Attributs de XMLHttpRequest

  1. l’attribut readyState : Il indique l’état de réception des données :

    Valeur État Description
    0 xhr.UNSENT Le client a été créé. open() n’a pas encore été appelé.
    1 xhr.OPENED open() a été appelé.
    2 xhr.HEADERS_RECEIVED send() a été appelé, et les en-têtes et le statut sont disponibles.
    3 xhr.LOADING Téléchargement ; responseText contient des données partielles.
    4 xhr.DONE L’opération est terminée.
  2. l’attribut responseText

    Il contient, sous forme d’une chaîne de caractères, les données en réponse à la requête. Il n’est complet que si readyState est à la valeur 4.

  3. l’attribut status
    Code d’état de la réponse (par ex. 200 OK)

Requête synchrone avec XHR

Il suffit de :

  • créer une instance de la classe XMLHttpRequest
  • initialiser la requête et écriture son en-tête avec open
  • écrire le corps de la requête et l’envoyer avec send

Après send, la réponse HTTP (le status, le document …) est écrit dans ce même objet.

let req = new XMLHttpRequest();
req.open('GET', 'https://romainlebreton.github.io/', false); 
req.send(null); // null: corps de la requête vide si GET

console.log(req.status); // -> 200
console.log(req.responseText.substring(0,100)); 
// -> <!DOCTYPE html><html>...

Défaut d’une requête synchrone

Inconvénients d’une requête synchrone :

  • Le send est bloquant, c’est-à-dire que le JavaScript reste bloqué sur send tant que l’on n’a pas reçu la réponse du serveur.

  • C’est d’autant plus gênant que la connexion est mauvaise, le serveur est lent ou le fichier renvoyé est gros !

Remarque : C’est le false de req.open('GET', url, false) qui fait que la requête est synchrone.

Défaut d’une requête synchrone


Exemple de blocage avec une requête synchrone

url = "cityRequest.php?name=Vi";
let httpRequest = new XMLHttpRequest();
// false désactive l'asynchronisme
httpRequest.open("GET", url, false);
httpRequest.send(null);
console.log(httpRequest.response);


Les événements ne se déclenchent pas sur le navigateur ! Pourquoi ??

Réponse : Le code JavaScript s’exécute sans parallélisme dans le thread principal jusqu’à son terme. Il ne peut pas traiter d’évènements (clavier, animation, …) tant qu’il est occupé.

Requête asynchrone avec XHR

Programmation asynchrone :

  • style de programmation dans lequel les tâches sont exécutées en parallèle
  • permet à plusieurs tâches de s’exécuter en même temps, sans attendre la fin de chaque tâche avant de passer à la suivante.


Pour XMLHttpRequest :

On active l’asynchronisme avec req.open('GET', url, true)


Requête asynchrone avec XHR

Piège :

let req = new XMLHttpRequest(); 
req.open ("GET", "https://romainlebreton.github.io", true); 
req.send(null);
console.log("Réponse :" + req.responseText); 
// Réponse vide !

Solution : Il faut un mécanisme pour notifier au client que la requête est terminée : Écoute de l’événement "load"

Exemple :

let req = new XMLHttpRequest(); 
req.open ("GET", "https://romainlebreton.github.io", true); 
req.addEventListener("load", 
  function() { 
    console.log ("Réponse :" + req.responseText.substring(0,100)); 
  }
);
req.send(null);

Requête asynchrone POST

Pour ressembler à l’envoi d’un formulaire POST

let xhr = new XMLHttpRequest()
url = "https://webinfo.iutmontp.univ-montp2.fr/~rletud/traitementPost.php"
xhr.open("POST", url);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.addEventListener("load", 
  function() { 
    console.log ("Réponse :" + xhr.responseText.substring(0,100)); 
  }
);
xhr.send("nom_var=AssinMarc")

Autre façon de faire : utiliser les FormData

La boucle des évènements

Boucle des évènements

La programmation asynchrone amène des tâches à s’exécuter en parallèle :

  • Sur le thread principal : DOM, affichage, JavaScript
  • Sur des thread parallèles : réseau, clavier, souris, cryptographie

JavaScript gère la concurrence entre tâches parallèles grâce à une « boucle d’événements » :

  • Les callback des évènements asynchrones sont empilés dans la pile de tâches
  • Boucle des évènements (Event loop):
    • Exécution du JavaScript dans le thread principal jusqu’à son terme
    • Quand le thread principal n’a plus de code à exécuter, il dépile une tâche et l’exécute jusqu’à son terme
    • Et il boucle ainsi de suite

Boucle des évènements

Exemple : Qu’affiche le programme suivant ?

console.log("Étape 1.");
setTimeout(function etape2 () { console.log("Étape 2.");}, 0);
console.log("Étape 3.");

Réponse :

// Étape 1.
// Étape 3.
// Étape 2.

Pourquoi ?
Parce que le callback de setTimeout est rajouté sur la pile des tâches, et ne sera exécuté qu’à la fin du JavaScript “principal”.

Visualisation de la pile des tâches avec l’outil Loupe

Sources