Le modèle d'exécution de JavaScript est nuancé et facile à mal comprendre. En savoir plus sur la boucle d'événements à sa base peut aider.
JavaScript est un langage à thread unique, conçu pour gérer les tâches une par une. Cependant, la boucle d'événements permet à JavaScript de gérer les événements et les rappels de manière asynchrone en émulant des systèmes de programmation simultanés. Cela garantit les performances de vos applications JavaScript.
Qu'est-ce que la boucle d'événements JavaScript?
La boucle d'événements de JavaScript est un mécanisme qui s'exécute en arrière-plan de chaque application JavaScript. Il permet à JavaScript de gérer les tâches en séquence sans bloquer son thread d'exécution principal. Ceci est appelé programmation asynchrone.
La boucle d'événements conserve une file d'attente de tâches à exécuter et alimente ces tâches vers la droite API Web pour une exécution à la fois. JavaScript garde une trace de ces tâches et gère chacune en fonction du niveau de complexité de la tâche.
Comprendre la nécessité de la boucle d'événements JavaScript et de la programmation asynchrone. Vous devez comprendre quel problème cela résout essentiellement.
Prenez ce code, par exemple :
functionlongRunningFunction() {
// This function does something that takes a long time to execute.
for (var i = 0; i < 100000; i++) {
console.log("Hello")
}
}functionshortRunningFunction(a) {
return a * 2 ;
}functionmain() {
var startTime = Date.now();
longRunningFunction();
var endTime = Date.now();// Prints the amount of time it took to execute functions
console.log(shortRunningFunction(2));
console.log("Time taken: " + (endTime - startTime) + " milliseconds");
}
main();
Ce code définit d'abord une fonction appelée longRunningFunction(). Cette fonction effectuera une sorte de tâche complexe et chronophage. Dans ce cas, il effectue une pour boucle itérant plus de 100 000 fois. Cela signifie que console.log("Bonjour") tourne 100 000 fois.
Selon la vitesse de l'ordinateur, cela peut prendre beaucoup de temps et bloquer shortRunningFunction() de l'exécution immédiate jusqu'à la fin de la fonction précédente.
Pour le contexte, voici une comparaison du temps nécessaire pour exécuter les deux fonctions :
Et puis le single shortRunningFunction():
La différence entre une opération de 2 351 millisecondes et une opération de 0 milliseconde est évidente lorsque vous souhaitez créer une application performante.
Comment la boucle d'événements contribue aux performances de l'application
La boucle d'événements comporte différentes étapes et parties qui contribuent au fonctionnement du système.
La pile d'appels
La pile d'appels JavaScript est essentielle à la façon dont JavaScript gère les appels de fonction et d'événement de votre application. Le code JavaScript se compile de haut en bas. Cependant, Node.js, à la lecture du code, Node.js affectera les appels de fonction de bas en haut. Au fur et à mesure de sa lecture, il pousse les fonctions définies en tant que cadres dans la pile d'appels une par une.
La pile d'appels est responsable du maintien du contexte d'exécution et de l'ordre correct des fonctions. Pour ce faire, il fonctionne comme une pile LIFO (dernier entré, premier sorti).
Cela signifie que le dernier cadre de fonction que votre programme pousse sur la pile des appels sera le premier à sortir de la pile et à s'exécuter. Cela garantira que JavaScript maintient le bon ordre d'exécution des fonctions.
JavaScript supprimera chaque image de la pile jusqu'à ce qu'elle soit vide, ce qui signifie que toutes les fonctions ont fini de s'exécuter.
API Web Libuv
Au cœur des programmes asynchrones de JavaScript se trouve libuv. La bibliothèque libuv est écrite dans le langage de programmation C, qui peut interagir avec le système d'exploitation API de bas niveau. La bibliothèque fournira plusieurs API permettant au code JavaScript de s'exécuter en parallèle avec d'autres code. Des API pour créer des threads, une API pour communiquer entre les threads et une API pour gérer la synchronisation des threads.
Par exemple, lorsque vous utilisez setTimeout dans Node.js pour suspendre l'exécution. La minuterie est configurée via libuv, qui gère la boucle d'événements pour exécuter la fonction de rappel une fois le délai spécifié écoulé.
De même, lorsque vous effectuez des opérations réseau de manière asynchrone, libuv gère ces opérations de manière non bloquante. manière, en s'assurant que d'autres tâches peuvent continuer le traitement sans attendre que l'opération d'entrée/sortie (E/S) fin.
La file d'attente de rappel et d'événements
La file d'attente de rappel et d'événements est l'endroit où les fonctions de rappel attendent leur exécution. Lorsqu'une opération asynchrone se termine à partir de la libuv, sa fonction de rappel correspondante est ajoutée à cette file d'attente.
Voici comment se déroule la séquence :
- JavaScript déplace les tâches asynchrones vers libuv pour qu'il les gère et continue de gérer la tâche suivante immédiatement.
- Lorsque la tâche asynchrone se termine, JavaScript ajoute sa fonction de rappel à la file d'attente de rappel.
- JavaScript continue d'exécuter d'autres tâches dans la pile d'appels jusqu'à ce qu'il en ait fini avec tout dans l'ordre actuel.
- Une fois la pile d'appels vide, JavaScript examine la file d'attente de rappel.
- S'il y a un rappel dans la file d'attente, il pousse le premier sur la pile des appels et l'exécute.
De cette façon, les tâches asynchrones ne bloquent pas le thread principal et la file d'attente de rappel garantit que leurs rappels correspondants s'exécutent dans l'ordre dans lequel ils se sont terminés.
Le cycle de la boucle d'événements
La boucle d'événements a également quelque chose appelé la file d'attente des microtâches. Cette file d'attente spéciale dans la boucle d'événements contient des microtâches programmées pour s'exécuter dès que la tâche en cours dans la pile d'appels est terminée. Cette exécution se produit avant la prochaine itération de rendu ou de boucle d'événement. Les microtâches sont des tâches hautement prioritaires qui ont priorité sur les tâches régulières dans la boucle d'événements.
Une microtâche est généralement créée lorsque vous travaillez avec Promises. Chaque fois qu'une promesse est résolue ou rejetée, son correspondant .alors() ou .attraper() callbacks rejoint la file d'attente des microtâches. Vous pouvez utiliser cette file d'attente pour gérer les tâches nécessitant une exécution immédiate après l'opération en cours, telles que la mise à jour de l'interface utilisateur de votre application ou la gestion des changements d'état.
Par exemple, une application Web qui effectue la récupération de données et met à jour l'interface utilisateur en fonction des données récupérées. Les utilisateurs peuvent déclencher cette récupération de données en cliquant plusieurs fois sur un bouton. Chaque clic sur un bouton lance une opération de récupération de données asynchrone.
Sans les microtâches, la boucle d'événements de cette tâche fonctionnerait comme suit :
- L'utilisateur clique plusieurs fois sur le bouton.
- Chaque clic sur un bouton déclenche une opération de récupération de données asynchrone.
- Au fur et à mesure que les opérations de récupération de données sont terminées, JavaScript ajoute leurs rappels correspondants à la file d'attente des tâches habituelles.
- La boucle d'événements démarre le traitement des tâches dans la file d'attente de tâches normale.
- La mise à jour de l'interface utilisateur basée sur les résultats de l'extraction de données s'exécute dès que les tâches régulières le permettent.
Cependant, avec les microtâches, la boucle d'événements fonctionne différemment :
- L'utilisateur clique plusieurs fois sur le bouton et déclenche une opération d'extraction de données asynchrone.
- Au fur et à mesure que les opérations d'extraction de données sont terminées, la boucle d'événements ajoute leurs rappels correspondants à la file d'attente des microtâches.
- La boucle d'événements démarre le traitement des tâches dans la file d'attente des microtâches immédiatement après avoir terminé la tâche en cours (clic de bouton).
- La mise à jour de l'interface utilisateur basée sur les résultats de l'extraction de données s'exécute avant la prochaine tâche régulière, offrant une expérience utilisateur plus réactive.
Voici un exemple de code :
const fetchData = () => {
returnnewPromise(resolve => {
setTimeout(() => resolve('Data from fetch'), 2000);
});
};
document.getElementById('fetch-button').addEventListener('click', () => {
fetchData().then(data => {
// This UI update will run before the next rendering cycle
updateUI(data);
});
});
Dans cet exemple, chaque clic sur le bouton "Récupérer" appelle récupérer les données (). Chaque opération d'extraction de données est planifiée comme une microtâche. En fonction des données extraites, la mise à jour de l'interface utilisateur s'exécute immédiatement après la fin de chaque opération d'extraction, avant toute autre tâche de rendu ou de boucle d'événement.
Cela garantit que les utilisateurs voient les données mises à jour sans subir de retards dus à d'autres tâches dans la boucle d'événements.
L'utilisation de microtâches dans des scénarios comme celui-ci peut empêcher le blocage de l'interface utilisateur et fournir des interactions plus rapides et plus fluides dans votre application.
Implications de la boucle d'événements pour le développement Web
Comprendre la boucle d'événements et comment utiliser ses fonctionnalités est essentiel pour créer des applications performantes et réactives. La boucle d'événements fournit des fonctionnalités asynchrones et parallèles, ce qui vous permet de gérer efficacement des tâches complexes dans votre application sans compromettre l'expérience utilisateur.
Node.js fournit tout ce dont vous avez besoin, y compris les travailleurs Web pour obtenir un parallélisme supplémentaire en dehors du fil principal de JavaScript.