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"); }
);
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 :
Les fonctions plus récentes n'attendent pas de callback en paramètre, mais retournent un objet Promesse.
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.then
se "propage" le long de la chaîne,
ce qui permet à un catch
de ratrapper toutes les erreurs produites au dessus de lui.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);
});
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")} )
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"
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.
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
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
.
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