Programmation Javascript avancée§

Promesses§

Rappels sur la programmation asynchrone§

Au cours précédent, on a vu qu’en programmation asynchrone, il était fréquent de passer une fonction en paramètre d’une autre fonction, afin que la première soit appelée plus tard.

Exemple 1 :

b.addEventListener('click',
    (evt) => { console.log("Vous avez cliqué"); }
);

Exemple 2 :

setTimeout(
    () => { console.log("Une seconde s'est écoulée"); }
, 1000);

Exemple 3 :

fetch("http://example.org/").then(
    (resp) => { console.log("Le serveur a répondu"); }
).catch(
    (err) => { console.log("Le serveur ne répond pas"); }
);

Callback§

Historiquement, la fonction a appeler plus tard est passée directement en paramètre de la fonction appelante (exemples 1 et 2 ci-avant). On appelle la fonction passée en paramètre un callback.

Parfois, une fonction requiert deux callbacks :

  • un à appeler en cas de succès,
  • et un à appeler en cas d’erreur.

Les fonctions plus récentes n’attendent pas de callback en paramètre, mais retournent un objet Promesse.

Promesse§

  • Une promesse est un objet représentant un traitement asynchrone.
  • Elle possède trois méthodes principales, qui attendent toutes un callback en paramètre :
    • then : callback à exécuter en cas de succès ;
    • catch : callback a exécuter en cas d’erreur ;
    • finally : callback à exécuter dans tous les cas.
  • Ces méthodes retournent à leur tour une promesse (encapsulant la valeur retournée par le callback) ce qui permet d’en enchaîner plusieurs.
  • Une erreur dans l’un des callbacks passés à then se “propage” le long de la chaîne, ce qui permet à un catch de ratrapper toutes les erreurs produites au dessus de lui.

Avantage : lisibilité accrue§

  • Exemple avec des callback anonymes :

    setTimeout(() => {
      console.log("un");
      setTimeout(() => {
        console.log("deux");
        setTimeout(() => {
          console.log("trois");
        }, 3000);
      }, 1000);
    }, 2000);
    console.log("coucou");
    

    Note

    À votre avis, dans quel ordre s’affiche le texte, et avec quel délais ?

  • Alternative avec des fonctions nommées en callback :

    setTimeout(étape1, 2000);
    
    function étape1() {
      console.log("un");
      setTimeout(étape2, 1000);
    }
    
    function étape2() {
      console.log("deux");
      setTimeout(étape3, 3000);
    }
    
    function étape3() {
      console.log("trois");
    }
    

    Note

    Cette version est plus lisible, mais verbeuse, et oblige à donner un nom à chaque étape.

  • Exemple avec une hypothétique fonction sleep, qui prendrait en paramètre un délais et retournerait une promesse :

    sleep(2000)
    .then(() => {
      console.log("one");
      return sleep(1000);
    }).then(() => {
      console.log("two");
      return sleep(3000);
    }).then(() => {
      console.log("three");
    });
    console.log("coucou");
    

Indice

Attention de ne pas oublier le return à la fin de chaque callback passé à then, sans quoi il retournera null, et le then suivant s’exécutera immédiatement.

  • Exemple avec fetch :

    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);
    });
    

Avantage : combinaison de promesses§

Attendre que toutes les promesses d’un tableau aient abouti :

Promise.all(
  [fetch(url1), fetch(url2), fetch(url3)]
).then(
  (responses) => { console.log("toutes les URLs ont répondu"); }
).catch(
  (err) => { console.log("une erreur s'est produite", err); }
)

Attendre qu’au moins une des promesses d’un tableau ait abouti :

Promise.race(
  [fetch(url1), fetch(url2), fetch(url3)]
).then(
  (response) => { console.log("une des URL a répondu")}
)

Programmation orientée-objet§

Méthodes d’objet§

  • On a vu précédemment comment créer un objet en Javascript :

    let v = {
      x: 3,
      y: 4,
    };
    console.log(v.x, v.y);
    
  • On a également vu qu’une fonction anonyme peut être affectée à une variable, comme n’importe quelle autre valeur :

    let x = {
      greet: function(name) { console.log("hello", name)}
    }
    x.greet("world"); // affiche "hello world"
    

Le mot-clé this§

En Javascript, lorsqu’on accède à une fonction via un objet, le mot-clé this dénote cet objet :

let v = {
  x: 3.0,
  y: 4.0,
  length: function() {
    return Math.hypot(this.x, this.y);
  }
}
v.length(); // retourne 5

Note

Les fonctions “flêches” font exception à cette règle, c’est pourquoi on utilise le mot-clé function ici.

Méthodes ES6§

En ES6, on peut également utiliser la syntaxe raccourcie suivante :

let v = {
  x: 3.0,
  y: 4.0,
  length() {
    return Math.hypot(this.x, this.y);
  }
}
v.length(); // retourne 5

Classes ES6§

En ES6, il est possible de définir une classe, pour créer plusieurs objets ayant la même structure et les mêmes méthodes.

class Vector {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  length() {
    return Math.hypot(this.x, this.y);
  }
}

let v = new Vector(3, 4);
v.length(); // retourne 5

Note

constructor est une méthode particulière, appelée lorsqu’on utilise l’opérateur new.

Attributs contrôlés par des méthodes§

Dans l’exemple précédent, on pourrait souhaiter utiliser length comme un attribut (en lecture seule) plutôt que comme une méthode (i.e. sans les parenthèses).

Ceci est possible en définissant un accesseur (getter en anglais) :

class Vector {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  get length() {
    return Math.hypot(this.x, this.y);
  }
}

let v = new Vector(3, 4);
v.length; // retourne 5