par , 01/08/2016 � 09h00 (8608 Affichages)
JavaScript bien qu'omnipr�sent de nos jours reste un langage tr�s d�cri� � cause notamment de nombreuses approximations dans les sp�cifications de sa syntaxe. Mais il serait simpliste et erron� de croire que le succ�s de ce langage n'est d� qu'� un ph�nom�ne de mode (cela fait une vingtaine d'ann�es que cela dure), ou que ses partisans sont de mauvais d�veloppeurs r�pandant de mauvaises pratiques de programmation.
Car au-del� des concepts phares comme l'h�ritage par prototype qui est � mon go�t survendu, la v�ritable force du langage ne r�side pas vraiment en lui, mais autour. Cette force, c'est son fonctionnement asynchrone dont les fondements ne sont pas toujours bien compris.
JavaScript : monothread et asynchrone
JavaScript est un langage qui a �t� pens� pour �voluer dans un environnement monothread et asynchrone.
Le terme thread pourrait �tre traduit en premi�re approximation par processus. Le fait que le moteur JavaScript (grosso modo l'interpr�teur JavaScript) soit monothread signifie que le moteur ne peut interpr�ter qu'une seule et unique instruction � la fois, via sa seule et unique pile d'ex�cution (Call Stack). Le principal avantage de ceci �tant la simplicit�.
Le terme asynchrone fait r�f�rence au comportement de certains traitements dans JavaScript qui peuvent �tre d�l�gu�s en dehors du moteur. Dans le cas d'une page Web, les traitements asynchrones seront d�l�gu�s au navigateur. � noter que bien que le moteur JavaScript soit monothread, l'h�te (eg. le navigateur ou NodeJS) peut lui �tre multithreads. Les threads pouvant �tre l'application JavaScript en cours, le rendu de l'interface utilisateur (UI), la gestion des entr�es/sorties (IO), etc.
Sans cette possibilit� de r�aliser des appels asynchrones, une application JavaScript serait condamn�e � bloquer le navigateur le temps de son ex�cution.
Notification de blocage de Firefox par un script
Circuit de l'asynchrone
L'asynchrone en JavaScript repose sur un circuit partant de la pile d'ex�cution, sortant du moteur JavaScript via les API du syst�me h�te (Host APIs) qui peut �tre le navigateur ou NodeJS, la file d'attente des callbacks (Callback Queue), la boucle des �v�nements (Event Loop), pour enfin revenir au moteur JavaScript sur la pile d'ex�cution.
Il est important de noter que le seul composant du moteur JavaScript directement impliqu� dans le circuit de l'asynchrone est la pile d'ex�cution. Les autres composants que sont les API du syst�me h�te, la file d'attente des callbacks et la boucle des �v�nements ne font pas � proprement parler partie de ce moteur JavaScript.
Il est ainsi possible de dire que JavaScript est un peu plus qu'un langage, c'est aussi une architecture.
Composants du circuit de l'asynchrone
Pile d'ex�cution
Un programme comporte g�n�ralement des fonctions appelant d'autres fonctions (ou s'appelant elles-m�mes). Le r�le de la pile d'ex�cution est d'assurer le suivi de la cha�ne d'appel en m�morisant la fonction et son contexte d'ex�cution (variables locales et param�tres d'appel). Un appel d'une fonction A() dans le contexte global empilera un premier niveau sur la pile d'ex�cution. Si cette fonction A() appelle en son sein une fonction B(), alors un second niveau sera empil� sur la pile. Et si cette fonction B() appelle une fonction C(), alors un troisi�me niveau sera empil� sur la pile. Un niveau de la pile n'est retir� que lorsque la fonction appel�e a termin� son ex�cution. C'est pourquoi lors d'appels r�cursifs mal impl�ment�s, il est possible de d�passer la capacit� de la pile et obtenir le fameux message d'erreur � stack overflow �.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function A() {
// Etat 1
B();
// Etat 5
}
function B() {
// Etat 2
C();
// Etat 4
}
function C() {
// Etat 3
}
// Etat 0
A();
// Etat 6 |
Appels imbriqu�s de fonctions
�tats successifs de la pile d'ex�cution lors d'appels imbriqu�s
APIs du syst�me h�te
Le syst�me h�te (eg. navigateur, NodeJS) fournit toute une panoplie de fonctions et d'objets (API) au moteur JavaScript pour interagir avec lui ou avec le syst�me d'exploitation. Certaines de ces fonctions sont asynchrones comme XMLHttpRequest.send(), FileReader.readFile() dans le domaine des acc�s aux ressources ou encore le listener du DOM pour ce qui est de la gestion des �v�nements, asynchrones par essence.
Le programme principal JavaScript doit avoir la possibilit� de savoir si un traitement asynchrone qu'il a appel� est termin� puis d'exploiter le r�sultat de ce traitement asynchrone en cons�quence. C'est l� qu'interviennent les fonctions callbacks. Il est courant en JavaScript que les callbacks soient des fonctions anonymes pass�es en param�tre et d�finies au moment m�me de l'appel de la fonction asynchrone.
1 2 3 4 5 6 7 8 9 10 11
| var req = new XMLHttpRequest();
req.open('GET', 'http://www.mozilla.org/', true);
req.onreadystatechange = function callback(aEvt) { // fonction callback
if (req.readyState == 4) {
if(req.status == 200)
dump(req.responseText);
else
dump("Erreur pendant le chargement de la page.\n");
}
};
req.send(null); // traitement asynchrone |
Requ�te HTTP asynchrone au format XML sous un navigateur
1 2 3 4 5 6
| var fs = require('fs');
// traitement asynchrone
fs.readFile('DATA', 'utf8', function callback(err, contents) { // fonction callback
console.log(contents);
}); |
Lecture asynchrone d'un fichier sous NodeJS
File d'attente des �v�nements
Une fois que le traitement asynchrone est termin� ou qu'un �v�nement particulier survient, la callback qui a �t� fournie en param�tre est ins�r�e dans la file d'attente des callbacks avant d'�tre prise en compte par la boucle des �v�nements.
Boucle des �v�nements
La boucle des �v�nements a pour but de surveiller l'�tat de la pile d'ex�cution. Si cette derni�re est vide d'instruction � ex�cuter, la boucle des �v�nements d�placera la callback en attente dans la file vers la pile d'ex�cution, permettant � cette callback d'�tre ainsi ex�cut�e.
D�roulement
Pour mieux illustrer l'implication des diff�rents composants dans un appel asynchrone, nous allons nous d�finir une fonction getData() qui appellera la fonction asynchrone standard setTimeout().
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| var k = 0;
// Définition de la fonction getData()
function getData(callback) {
console.log('getData()');
setTimeout(function setTimeoutCB(counter) { // callback asynchrone
console.log('setTimeoutCB()');
callback(null, counter);
}, 500, ++k);
}
// Appel de la fonction getData()
getData(function getDataCB(error, data) { // callback synchrone
console.log('getDataCB()');
console.log('data: ', data);
}); |
On peut constater dans le code ci-dessus, que la fonction setTimeout() appel�e � l'int�rieur de la fonction getData() appelle une fonction callback que nous avons nomm�e setTimeoutCB() pour la clart� du propos, mais qui aurait tr�s bien pu �tre anonyme.
Cette fonction setTimeoutCB() a ici pour t�che d'appeler la fonction callback() qui est un param�tre de la fonction getData(). C'est une des caract�ristiques des fonctions callbacks.
Lors de l'appel de la fonction getData(), une fonction callback getDataCB() lui est fournie en param�tre.
Regardons ce qu'il se passe lors de cet appel.
(1) Apr�s que les fonctions getData() puis setTimeout() ont �t� empil�es sur la pile d'ex�cution, la fonction setTimeout() qui fait partie de l'API du navigateur, est appel�e et est prise en charge par celui-ci de fa�on asynchrone. � noter que cet appel � la fonction setTimeout() est la derni�re instruction directement ex�cut�e par le programme principal qui arrive ici � son terme. � ce moment, la pile d'ex�cution est vide.
(2) Le d�compte du d�lai s'effectue dans un thread � part, et lorsque le d�lai est �puis�, la fonction callback qui a �t� pass�e en param�tre de setTimeout(), setTimeoutCB(), est envoy�e vers la file d'attente. Notons que les param�tres de setTimeoutCB() sont fournis par setTimeout(). Ici, il s'agit juste d'un compteur num�rique valant 1, mais avec d'autres fonctions asynchrones, ce pourrait �tre un message d'erreur ou des donn�es binaires. L'important est d'avoir � l'esprit que les param�tres de la fonction callback sont fournis par la fonction asynchrone appelante.
(3) La boucle des �v�nements d�tecte la pr�sence d'une callback, setTimeoutCB(), dans la file d'attente, et s'assure que la pile d'ex�cution est bien vide. Si c'est le cas, la boucle des �v�nements envoie la callback sur la pile d'ex�cution o� elle est ex�cut�e.
Suite � cela, ce qui n'est pas sch�matis�, c'est l'empilement par dessus setTimeoutCB(), de la fonction callback() qui est le param�tre de getData() faisant r�f�rence � getDataCB(). Le fait que setTimeoutCB() ait toujours acc�s au param�tre callback vient de la propri�t� des fermetures en JavaScript (cf. article). La fonction setTimeoutCB() n'�tant pas elle une fonction asynchrone, contrairement � setTimeout(), il n'y a pas d'appel � l'API du navigateur, pas de passage par la file d'attente et pas d'intervention de la boucle des �v�nements pour ex�cuter la fonction. Il s'agit l� de traitements synchrones qui restent donc sous l'enti�re responsabilit� du moteur JavaScript.
Remarque : N'h�sitez pas � relire plusieurs fois le code, le sch�ma et les explications pour bien visualiser le d�roulement.
Conclusion
Les m�canismes de l'asynchrone en JavaScript ne sont pas tr�s compliqu�s � comprendre, mais non triviaux � expliquer comme on vient de le voir. Il n'existe finalement pas �norm�ment de ressources � ce sujet sur le Web. J'esp�re tout de m�me que ce bref expos� vous aura aid� � mieux comprendre ce qui se d�roule � sous le capot � lorsque vous utiliserez des fonctions asynchrones et des callbacks.
Il existe un outil en ligne sur le Web qui permet de visualiser dynamiquement le d�roulement d'un code asynchrone. Son auteur, Philip Roberts, explique en anglais plus en d�tail le fonctionnement de l'asynchrone dans cette vid�o ci-dessous et dont cet article s'est largement inspir�.
N'h�sitez pas � partager ce billet si vous l'avez trouv� utile.
Bon d�veloppement !