Voir le flux RSS

Le Blog d'un Ninja codeur

[Actualité] Les bases de l'asynchrone en JavaScript

Note : 6 votes pour une moyenne de 5,00.
par , 01/08/2016 à 10h00 (1504 Affichages)
Nom : 1024.png
Affichages : 6542
Taille : 17,1 Ko

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.

Nom : jsblocking.png
Affichages : 5177
Taille : 10,4 Ko
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.

Nom : circuitasynchrone_empty.png
Affichages : 5160
Taille : 24,2 Ko
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 ».
Code javascript : Sélectionner tout - Visualiser dans une fenêtre à part
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

Nom : piledexecution.png
Affichages : 5157
Taille : 15,9 Ko

É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.

Code javascript : Sélectionner tout - Visualiser dans une fenêtre à part
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

Code javascript : Sélectionner tout - Visualiser dans une fenêtre à part
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().

Code javascript : Sélectionner tout - Visualiser dans une fenêtre à part
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.

Nom : circuitasynchrone.png
Affichages : 5884
Taille : 35,5 Ko

(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 !

Envoyer le billet « Les bases de l'asynchrone en JavaScript » dans le blog Viadeo Envoyer le billet « Les bases de l'asynchrone en JavaScript » dans le blog Twitter Envoyer le billet « Les bases de l'asynchrone en JavaScript » dans le blog Google Envoyer le billet « Les bases de l'asynchrone en JavaScript » dans le blog Facebook Envoyer le billet « Les bases de l'asynchrone en JavaScript » dans le blog Digg Envoyer le billet « Les bases de l'asynchrone en JavaScript » dans le blog Delicious Envoyer le billet « Les bases de l'asynchrone en JavaScript » dans le blog MySpace Envoyer le billet « Les bases de l'asynchrone en JavaScript » dans le blog Yahoo

Mis à jour 03/08/2016 à 18h02 par ClaudeLELOUP

Catégories
Développement , Javascript , Développement Web , DotNET , C#

Commentaires

  1. Avatar de Songbird_
    • |
    • permalink
    Salut salut,

    Pourquoi avoir tagué cet article dans la section Java et JavaScript ?

    Ça ne concerne en rien le langage Java.
  2. Avatar de yahiko
    • |
    • permalink
    Citation Envoyé par Songbird_
    Salut salut,

    Pourquoi avoir tagué cet article dans la section Java et JavaScript ?

    Ça ne concerne en rien le langage Java.
    Bonjour,

    Sauf erreur de ma part, je ne vois pas où est-ce que cet article est taggé en Java.
  3. Avatar de Songbird_
    • |
    • permalink

    Pourtant je vois ceci de mon côté:

  4. Avatar de yahiko
    • |
    • permalink
    Citation Envoyé par Songbird_

    Pourtant je vois ceci de mon côté:

    S'il s'agit de la page Web qui reprend l'article de mon blog, je n'ai aucun contrôle dessus. Je t'invite à contacter un responsable, comme vermine par exemple, pour avoir plus de précision sur le pourquoi du comment.
  5. Avatar de Songbird_
    • |
    • permalink
    Citation Envoyé par yahiko
    S'il s'agit de la page Web qui reprend l'article de mon blog, je n'ai aucun contrôle dessus. Je t'invite à contacter un responsable, comme vermine par exemple, pour avoir plus de précision sur le pourquoi du comment.
    D'accord, autant pour moi alors.
    Après tout ce n'est pas si grave, mais si ça pouvait être modifié dans l'immédiat, j'ai préféré te le signaler.

    Bonne journée !