En JavaScript, nous pouvons exécuter du code de manière synchrone (bloquant) ou asynchrone (non bloquant).
Prenons une fonction logValue
, qui prend une valeur et l'affiche dans la
console :
function logValue(value) {
console.log(value);
}
Nous allons passer logValue
comme callback à la méthode
Array.prototype.forEach
, qui va l'exécuter de manière synchrone :
const arrayOfValues = [1, 2, 3, 4, 5]
arrayOfValues.forEach(logValue)
// Log 1, 2, 3, 4 puis 5
On peut également passer logValue
comme callback de setTimeout
, qui va
l'exécuter de manière asynchrone :
setTimeout(logValue, 3000, "Hello world!")
logValue("How are you?")
// Log "How are you?"
// Log "Hello world!" 3 secondes plus tard
Une fonction est agnostique: elle peut être appelée de manière synchrone ou asynchrone, c'est la façon dont elle est exécutée qui définira le "mode".
Un cas où il est utile d'utiliser des APIs asynchrones avec Node.js: les accès au file-system. Si vous lisez un gros fichier en mode synchrone, il va bloquer l'exécution de votre programme tant qu'il n'a pas fini, mieux vaut attendre qu'il vous l'envoie quand il est prêt.
Node.js propose l'API suivante: fs.readFile(fileToRead, options, callback)
fs.readFile("./alphabet.txt", {encoding: "utf-8"}, (err, data) => {
if (err) {
onError(err)
} else {
onData(data)
}
})
Cet exemple montre une API utilisant un simple callback qui est exécuté lorsque
readFile
a lu le fichier ou échoué à le faire.
Un autre cas où utiliser des APIs asynchrones est particulièrement important : les appels réseaux. On ne peut pas se permettre de freeze toute notre interface pendant que la requête réseau tourne.
function logValue(value) { console.log(value) }
function logError(err) { console.error(err) }
fetch("https://api.github.com/users/wyeo")
.then(res => res.json())
.then(logValue) // Log le payload JSON lorsque la requête est terminée
.catch(logError) // Lance une erreur dans la console si quelque chose s'est mal passé
Dans cet exemple, l'API renvoie une Promise
:
une structure représentant une valeur potentielle.
Lorsque sa valeur est disponible, la promesse est remplie, et exécutera les
callbacks qu'on lui a passé dans .then
, si elle constate une erreur, elle
exécutera les callbacks qu'on lui a passé dans .catch
.
Les Promise
ne permettent cependant pas de traiter de la donnée au fur et à
mesure de son arrivée: elle est remplie une seule fois.
C'est là que les Observable
arrivent à la rescousse.
Un Observable
est un objet implémentant une méthode .subscribe
qui prend
comme paramètre un Observer
. Ce dernier a cette forme :
const observer = {
next: val => console.log(val), // une fonction à exécuter à chaque nouvel évenement
error: err => console.error(err), // une fonction à exécuter en cas d'erreur
complete: () => console.info("Complete!") // une fonction à exécuter lorsque l'observable a fini
};
Implémentons naïvement un Observable
qui va réagir lorsqu'un user va taper sur
son clavier et se considérer terminé une fois Enter
pressé:
const KeyboardObservable = {
subscribe: observer => {
const handleKeyUp = event => {
if (typeof event.keyCode === "number") {
if (event.keyCode === 13 /* Enter */) {
document.removeEventListener("keyup", handleKeyUp);
observer.complete();
} else {
observer.next(event.keyCode);
}
} else {
observer.error(new Error("No keyCode found"));
}
};
document.addEventListener("keyup", handleKeyUp);
// subscribe retourne la "soucription", contenant une fonction pour la stopper
return {
unsubscribe: () => document.removeEventListener("keyup", handleKeyUp)
};
}
};
let keys = [];
KeyboardObservable.subscribe({
next: keyCode => keys.push(String.fromCharCode(keyCode)),
error: error => console.error(error),
complete: () => alert(keys.join(""))
});
Un Observable
fonctionne à la fois pour du code synchrone et asynchrone, et il
s'agit d'un pattern qui peut s'appliquer à des cas où Promise
manque de
granularité, puisqu'il permet de traiter la donnée au fur et à mesure de son
arrivée. In fine, un observable est un event emitter avec un concept de
completion.
Il existe d'ailleurs un proposal en stage 1 pour en faire une API de la specification de JavaScript. On peut très bien imaginer que les observables deviennent une interface très répandue dans un futur proche.
Dans les prochains articles, nous verrons pourquoi et comment combiner des observables ainsi que les cas d'usage au sein d'une application React.