Introduction à la programmation asynchrone en JavaScript
JavaScript est un langage de programmation monothread avec un modèle d'entrée-sortie non bloquant. Cela signifie qu'une seule opération est exécutée à la fois. Cependant, grâce à la programmation asynchrone, Node.js peut traiter des milliers de requêtes simultanément sans bloquer le thread principal. Dans cet article, nous allons aborder les concepts fondamentaux de l'asynchronisme : les callbacks, les Promises et la syntaxe async/await.
Qu'est-ce que l'asynchronisme et pourquoi en avons-nous besoin ?
Imaginez que vous commandez une pizza. Si vous restez debout au comptoir en attendant qu'elle soit préparée, c'est du code synchrone. Tout le reste s'arrête. L'approche asynchrone : vous passez commande et vaquez à vos occupations. Quand la pizza est prête, on vous appelle. JavaScript fonctionne exactement de cette manière : il n'attend pas la fin d'une opération (lecture de fichier, requête serveur), mais continue d'exécuter le code suivant. Lorsque l'opération se termine, une fonction spéciale est appelée — un callback.
Dans Node.js, l'asynchronisme est particulièrement important car le serveur doit gérer simultanément de nombreux clients. Sans lui, chaque nouvelle requête bloquerait le fonctionnement de toute l'application.
Code synchrone vs asynchrone : exemple
Prenons un exemple simple de lecture de fichier. La version synchrone bloque l'exécution :
const fs = require('fs');
// Lecture synchrone — bloque le thread
const data = fs.readFileSync('file.txt', 'utf8');
console.log('Données lues :', data);
console.log('Ce code ne s\'exécutera qu\'après la lecture du fichier');
La version asynchrone ne bloque pas :
const fs = require('fs');
// Lecture asynchrone — ne bloque pas
fs.readFile('file.txt', 'utf8', (err, data) => {
if (err) throw err;
console.log('Données lues :', data);
});
console.log('Ce code s\'exécutera immédiatement, sans attendre la lecture du fichier');
Principales méthodes de gestion de l'asynchronisme
En JavaScript (et Node.js), il existe trois approches principales de la programmation asynchrone. Nous allons les examiner de la plus ancienne à la plus moderne.
1. Callback (fonctions de rappel)
Un callback est une fonction qui est passée à une autre fonction et appelée après la fin d'une opération asynchrone. C'est la méthode la plus basique et la plus ancienne.
function getUserData(userId, callback) {
setTimeout(() => {
const user = { id: userId, name: 'Anna' };
callback(null, user); // premier argument — erreur (si présente)
}, 1000);
}
getUserData(1, (err, user) => {
if (err) {
console.error('Erreur :', err);
return;
}
console.log('Utilisateur :', user.name);
});
Problème : lorsque les opérations se multiplient, le code se transforme en "callback hell" — des fonctions profondément imbriquées, difficiles à lire et à déboguer.
2. Promise (Promesses)
Une Promise est un objet qui représente le résultat d'une opération asynchrone. Elle peut se trouver dans l'un des trois états suivants :
- pending (en attente) — l'opération n'est pas encore terminée
- fulfilled (tenue) — l'opération s'est terminée avec succès
- rejected (rejetée) — l'opération s'est terminée par une erreur
Les Promesses résolvent le problème du callback hell et rendent le code plus linéaire :
function getUserData(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const user = { id: userId, name: 'Ivan' };
// resolve — succès, reject — erreur
resolve(user);
// reject(new Error('Utilisateur non trouvé'));
}, 1000);
});
}
// Utilisation de la chaîne .then()
getUserData(1)
.then(user => {
console.log('Utilisateur :', user.name);
return user.id;
})
.then(id => console.log('ID :', id))
.catch(err => console.error('Erreur :', err))
.finally(() => console.log('Opération terminée'));