Programmation asynchrone§

Fonction anonyme§

Objets de premier niveau§

  • En Javascript, les fonctions sont des objets de premier niveau, ce qui signifie qu’ils peuvent être manipulés au même titre que, par exemple, les entiers, les tableaux ou les objets.
  • On peut par exemple
    • affecter une fonction à une variable ;
    • la passer en paramètre d’une autre fonction (par exemple addEventListener) ;
    • l’affecter comme attribut à un objet (ce qui est une manière de doter un objet de méthodes, comme en Python).

Fonctions anonymes§

  • Javascript autorise la création de fonctions anonymes :

    function (param1, param2) { /* instructions here */ }
    
  • Contrairement à une déclaration de fonction, une fonction anonyme peut apparaître partout ou une fonction est acceptée.

  • C’est utile dans les cas ou cette fonction n’a pas vocation à être réutilisée ailleurs, en particulier pour les abonnements à des événements.

Exemple :

let b = document.querySelector("button");
b.addEventListener('click', function() {
  let i = document.getElementsByTagName("input")[0];
  i.value = Number(i.value) + 1;
});

Fonctions “flêches”§

En ES6, il existe une syntaxe plus compacte pour les fonctions anonymes. Au lieu d’écrire :

function (x, y) { return x+y; }

on peut utiliser la notation :

(x, y) => { return x+y; }

Dans le cas où la fonction comporte une unique instruction return, on peut même remplacer le corps de la fonction par l’expression à retourner :

(x, y) => x+y

Dans le cas où la fonction comporte exactement un argument, on peut ommettre les parenthèses autour de l’argument :

x => { console.log(x); return x+1; }

Autres exemples :

x => x+1
() => {
  console.log("Fonction flêche sans argument,");
  console.log("et comportant plusieurs instructions.");
}

Adaptation de l’exemple précédent :

let b = document.querySelector("button");
b.addEventListener('click', () => {
  let i = document.getElementsByTagName("input")[0];
  i.value = Number(i.value) + 1;
});

Note

Les fonctions “flêches” ne sont pas absolument équivalentes aux fonctions anonymes, mais la distinction concerne des notions non abordées dans ce chapitre.

Pour en savoir plus : https://stackoverflow.com/a/34361380

Modularité grâce aux fonctions anonymes§

Les variables déclarées hors de toute fonction, même préfixées par let, sont considérées par Javascript comme des variables globales.

Afin de contourner ce problème, il existe en Javascript une convention : on “enferme” tout le code du script dans une fonction anonyme, que l’on appelle immédiatement :

(function() {

// ... mon code Javascript ici ...

})();

Ainsi, on évite de polluer l’environnement global.

Autre exemple d’utilisation de fonctions anonymes§

let b1 = document.querySelector('button#b1');

b1.addEventListener("click", () => {
  let previousContent = b1.textContent;
  b1.textContent = "(en attente)";
  b1.disabled = true;
  setTimeout(() => {
    alert("message");
    b1.textContent = previousContent;
    b1.disabled = false;
  }, 2000);
});

Voir et modifier cet exemple.

Exécution asynchrone§

Motivation§

Dans l’exemple précédent, pourquoi utiliser la fonction setTimeout et non une fonction qui bloquerait l’exécution pendant 2s, comme on le ferait (par exemple) en Python, à l’image de l’exemple ci-dessous ?

// ⚠ MAUVAIS EXEMPLE ⚠
let previousContent = b1.textContent;
b1.textContent = "(en attente)";
b1.disabled = true;
sleep(2000); // fonction imaginaire (n'existe pas en JS)
alert("message");
b1.textContent = previousContent;
b1.disabled = false;

Note

La fonction sleep n’existe pas réellement en Javascript, et pour cause puisque ce n’est pas la bonne manière de faire.

  • Réponse : parce que le navigateur ne peut pas faire deux choses à la fois dans une page : gérer l’affichage du HTML et exécuter le code Javascript.
  • Pendant qu’une fonction Javascript s’exécute, la page est totalement “gelée” (saisie, définelement...).
  • Si la fonction garde la main pendant une durée trop longue, ce phénomène sera perceptible, et dégradera l’expérience utilisateur.

Pour aller plus loin§

http://latentflip.com/loupe/

Loupe vous permet de visualiser la manière dont les événements sont gérés en Javascript.

La vidéo qui sert d’introduction est également une bonne introductions aux mécanismes mis en œuvre.

Portée et fermeture§

Portée (scope)§

  • La portée d’une variable est la partie du code sur laquelle cette variable est définie.
  • Pour les variables globales, c’est l’ensemble des scripts exécutés par la page (y compris les scripts écrits par d’autres).
  • Pour une variable locale (let), c’est le bloc entre accolades dans lequel elle est déclarée.
  • Une variable locale d’une fonction f est donc accessible par les fonctions (anonymes ou non) définies à l’intérieur de f.

Fermeture (closure)§

  • Une fonction définie à l’intérieur d’une autre porte avec elle le contexte dans lequel elle a été créé, c’est à dire l’état des variables locales qui lui sont accessibles.
  • Une telle fonction assortie d’un contexte est appelée une fermeture.
  • Dans l’exemple précédent, la variable b1 est définie au chargement du script, mais « survit » à cette fonction, puisqu’elle est réutilisée au clic sur ce bouton, et encore deux secondes plus tard, dans le callback de la fonction setTimeout.
  • Visualisez l’exécution pas à pas d’un autre exemple de fermeture sur pythontutor.

Gestion avancée d’événements§

Cheminement d’un événement§

  • La plupart des événements se propagent dans l’arbre DOM.
  • Il est donc possible de s’abonner à tous les événements se produisant à l’intérieur d’un élément (et pas uniquement sur cet élément).
  • Par défaut, les listeners dont déclenchés à la remontée de l’événement.

Note

On peut également forcer un listener à se déclencher à la descente (capture d’un événement) plutôt qu’à sa remontée (bubbling). Pour cela on passera true en troisème paramètre de addEventListener.

Paramètre d’un listener§

  • Le listener reçoit en paramètre un objet événement, contenant notamment les attributs suivants :
    • target: l’élément le plus spécifique concerné par l’événement ;
    • type: le type d’événement (click, mouseover...)
document.getElementsByTagName("body")[0]
  .addEventListener("click", (evt) => {
    let msg = document.getElementById("msg");
    if (evt.target.tagName === "BUTTON") {
        msg.textContent = "Vous avez cliqué sur " +
           evt.target.textContent;
    } else {
        console.log(evt.target);
        msg.textContent = "Vous avez raté les boutons...";
    }
});

Voir et modifier cet exemple

AJAX§

Définition§

AJAX signifie

  • Asynchronous
  • Javascript
  • And
  • XML

... mais en pratique, on peut échanger n’importe quoi avec le serveur, pas uniquement du XML, et notamment du JSON.

Note

AJAX permet donc aux pages HTML d’un site de communiquer avec l’API JSON de ce même site.

Exemple simple§

fetch('http://example.org').then((response) => {
    // cette fonction est appelée lorsque
    // les en-têtes de la réponse ont été reçu
    if (!response.ok) {
        throw ("Error " + response.status);
    }
    return response.json() // attend la contenu
}).then((data) => {
    // cette fonction est appelée lorsque
    // le contenu de la réponse a été reçu,
    // et analysé comme du JSON
    do_something_with(data);
}).catch((err) => {
    // cette fonction est appelée en cas d'erreur
    console.log(err);
});

Note

La fonction fetch est disponible dans les navigateurs modernes.

Elle succède à l’ancienne méthode, nommée XMLHttpRequest, encore très utilisée.

La fonction fetch§

  • Elle prend en premier paramètre une URL.
  • Elle accepte en deuxième paramètre (facultatif) un objet, pouvant notamment contenir les attributs suivants :
    • method indique la méthode HTTP à utiliser (GET par défaut),
    • headers contient les en-têtes à utiliser (sous forme d’un objet JS),
    • body contient, le cas échéant, le contenu de la requête ;
    • plus d’information : http://devdocs.io/dom/request/request.
  • Elle retourne une Promesse.

Exemple de requête POST :

fetch("/api/Appointments/", {
    method: 'POST',
    headers: { "content-type": "application/json" },
    body: JSON.stringify(appointment_data),
}).then((response) => {
  if (!response.ok) { throw new Error("POST failed"); }
}).catch((err) => {
  alert(err);
});