Table of Contents
JavaScript Promises : Guide Complet de la Programmation Asynchrone
La programmation asynchrone est au coeur du developpement JavaScript moderne. Que vous fassiez des appels API, lisiez des fichiers ou gerez des interactions utilisateur, vous rencontrerez inevitablement du code asynchrone. Les Promises (promesses) representent l’evolution majeure qui a transforme la facon dont nous ecrivons ce code.
Pourquoi les Promises ? Le Probleme du Callback Hell
Avant les Promises, le code asynchrone en JavaScript reposait entierement sur les callbacks. Cela fonctionnait pour des cas simples, mais degenerait rapidement en ce qu’on appelle le “Callback Hell” ou “Pyramid of Doom” :
// Le redoutable Callback Hell
getUserData(userId, function(user) {
getOrders(user.id, function(orders) {
getOrderDetails(orders[0].id, function(details) {
getShippingInfo(details.shippingId, function(shipping) {
updateUI(user, orders, details, shipping, function() {
console.log('Tout est termine !');
}, function(error) {
console.error('Erreur UI:', error);
});
}, function(error) {
console.error('Erreur shipping:', error);
});
}, function(error) {
console.error('Erreur details:', error);
});
}, function(error) {
console.error('Erreur orders:', error);
});
}, function(error) {
console.error('Erreur user:', error);
});
Ce code presente plusieurs problemes majeurs :
- Lisibilite catastrophique : l’indentation excessive rend le code difficile a suivre
- Gestion d’erreurs fragmentee : chaque callback necessite sa propre gestion d’erreur
- Difficulte de maintenance : ajouter ou modifier une etape devient un cauchemar
- Inversion de controle : vous confiez l’execution de votre code a une fonction externe
Les Promises resolvent elegamment tous ces problemes en offrant une interface standardisee pour le code asynchrone.
Anatomie d’une Promise
Une Promise est un objet qui represente le resultat eventuel d’une operation asynchrone. Elle peut etre dans l’un des trois etats suivants :
Les Trois Etats d’une Promise
| Etat | Description | Transition possible |
|---|---|---|
| pending | Etat initial, l’operation est en cours | vers fulfilled ou rejected |
| fulfilled | L’operation a reussi avec une valeur | Etat final |
| rejected | L’operation a echoue avec une raison | Etat final |
Une fois qu’une Promise atteint l’etat fulfilled ou rejected, elle est dite “settled” (reglee) et ne peut plus changer d’etat. C’est une garantie fondamentale des Promises.
// Visualisation des etats
const pendingPromise = new Promise(() => {}); // Reste pending indefiniment
const fulfilledPromise = new Promise((resolve) => {
resolve('Succes !'); // Passe a fulfilled
});
const rejectedPromise = new Promise((_, reject) => {
reject(new Error('Echec !')); // Passe a rejected
});
Le Constructeur Promise
Le constructeur Promise prend une fonction executor avec deux parametres :
const maPromise = new Promise((resolve, reject) => {
// resolve(valeur) - appeler pour reussir la promesse
// reject(raison) - appeler pour echouer la promesse
});
Regles importantes de l’executor :
- L’executor s’execute immediatement et de maniere synchrone
- Seul le premier appel a
resolve()oureject()compte - Les appels suivants sont ignores silencieusement
- Si l’executor lance une exception, la Promise est automatiquement rejetee
// Exemple : seul le premier appel compte
const promise = new Promise((resolve, reject) => {
resolve('Premier'); // Cette valeur sera utilisee
resolve('Deuxieme'); // Ignore
reject('Erreur'); // Ignore aussi
});
promise.then(value => console.log(value)); // "Premier"
Les Methodes d’Instance : then(), catch(), finally()
then() - Gerer le succes (et l’echec)
La methode then() accepte jusqu’a deux callbacks :
promise.then(
(valeur) => { /* succes */ },
(raison) => { /* echec */ }
);
// Exemple pratique
fetchUser(123)
.then(
user => console.log('Utilisateur:', user.name),
error => console.error('Erreur:', error.message)
);
catch() - Gerer les erreurs
catch() est un raccourci pour then(undefined, onRejected) :
promise
.then(valeur => traiterValeur(valeur))
.catch(erreur => gererErreur(erreur));
Avantage de catch() sur le second argument de then() :
// Probleme : le handler d'erreur de then() ne capture pas
// les erreurs du handler de succes
promise.then(
value => { throw new Error('Oups !'); },
error => console.log('Pas capture !') // Cette erreur n'est PAS capturee ici
);
// Solution : utiliser catch() a la fin
promise
.then(value => { throw new Error('Oups !'); })
.catch(error => console.log('Capture !', error)); // Capture toutes les erreurs
finally() - Nettoyage garanti
finally() s’execute que la Promise soit fulfilled ou rejected, sans modifier le resultat :
let loadingData = true;
fetch('/api/data')
.then(response => response.json())
.then(data => processData(data))
.catch(error => showError(error))
.finally(() => {
loadingData = false;
hideLoadingSpinner();
});
Caracteristiques de finally() :
- Ne recoit aucun argument
- Ne modifie pas la valeur de resolution
- Ne modifie pas la raison de rejet
- Propage la Promise originale (sauf si elle lance une erreur)
Creation de Promises
Promise.resolve() et Promise.reject()
Ces methodes statiques creent des Promises deja reglees :
// Creer une Promise fulfilled
const resolved = Promise.resolve(42);
resolved.then(value => console.log(value)); // 42
// Creer une Promise rejected
const rejected = Promise.reject(new Error('Echec'));
rejected.catch(error => console.error(error.message)); // "Echec"
Cas special : Promise.resolve() avec une Promise
Si vous passez une Promise a Promise.resolve(), elle est retournee telle quelle :
const original = new Promise(resolve => setTimeout(() => resolve('OK'), 1000));
const wrapped = Promise.resolve(original);
console.log(original === wrapped); // true - meme reference !
Cas special : thenable
Un “thenable” est un objet avec une methode then(). Promise.resolve() le convertit en vraie Promise :
const thenable = {
then(resolve, reject) {
setTimeout(() => resolve('Je suis un thenable !'), 1000);
}
};
Promise.resolve(thenable)
.then(value => console.log(value)); // "Je suis un thenable !"
Promisifier des Callbacks
Le pattern de “promisification” transforme des API basees sur callbacks en Promises :
// API callback classique (style Node.js)
function readFileCallback(path, callback) {
// callback(error, data)
}
// Version promisifiee
function readFilePromise(path) {
return new Promise((resolve, reject) => {
readFileCallback(path, (error, data) => {
if (error) {
reject(error);
} else {
resolve(data);
}
});
});
}
// Utilisation
readFilePromise('/config.json')
.then(data => JSON.parse(data))
.then(config => console.log(config))
.catch(error => console.error('Erreur lecture:', error));
Fonction utilitaire generique de promisification :
function promisify(fn) {
return function(...args) {
return new Promise((resolve, reject) => {
fn(...args, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
};
}
// Utilisation
const readFile = promisify(fs.readFile);
const data = await readFile('file.txt', 'utf8');
setTimeout Promisifie : La Fonction delay()
Une des promisifications les plus utiles est celle de setTimeout :
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Utilisation simple
delay(2000).then(() => console.log('2 secondes ecoulees !'));
// Avec async/await
async function exemple() {
console.log('Debut');
await delay(1000);
console.log('1 seconde plus tard');
await delay(1000);
console.log('2 secondes plus tard');
}
Version avancee avec valeur et annulation :
function delay(ms, value) {
let timeoutId;
const promise = new Promise((resolve) => {
timeoutId = setTimeout(() => resolve(value), ms);
});
promise.cancel = () => clearTimeout(timeoutId);
return promise;
}
// Utilisation avec valeur
const result = await delay(1000, 'Valeur retournee');
console.log(result); // "Valeur retournee"
// Avec annulation
const delayedAction = delay(5000, 'Action');
delayedAction.cancel(); // Annule le timeout
Chainage de Promises
Le chainage est une des fonctionnalites les plus puissantes des Promises. Chaque appel a then(), catch() ou finally() retourne une nouvelle Promise.
Retourner des Valeurs vs Retourner des Promises
// Retourner une valeur simple
Promise.resolve(1)
.then(value => value + 1) // Retourne 2
.then(value => value * 2) // Retourne 4
.then(value => console.log(value)); // 4
// Retourner une Promise
Promise.resolve(1)
.then(value => Promise.resolve(value + 1)) // Retourne Promise<2>
.then(value => delay(1000).then(() => value * 2)) // Attend puis retourne 4
.then(value => console.log(value)); // 4 (apres 1 seconde)
Regle d’or : Si votre callback retourne une Promise, la chaine attend automatiquement sa resolution.
Exemple Pratique de Chainage
// Scenario : authentification et chargement de donnees utilisateur
function loginUser(credentials) {
return fetch('/api/login', {
method: 'POST',
body: JSON.stringify(credentials)
})
.then(response => {
if (!response.ok) {
throw new Error(`Erreur HTTP: ${response.status}`);
}
return response.json();
})
.then(data => {
localStorage.setItem('token', data.token);
return data.userId;
})
.then(userId => {
return fetch(`/api/users/${userId}`);
})
.then(response => response.json())
.then(user => {
console.log('Utilisateur connecte:', user.name);
return user;
})
.catch(error => {
console.error('Erreur de connexion:', error.message);
throw error; // Re-lancer pour permettre au code appelant de gerer
});
}
Propagation des Erreurs
Les erreurs se propagent a travers la chaine jusqu’au premier catch() :
Promise.resolve('debut')
.then(value => {
console.log('Etape 1:', value);
return 'etape 1 terminee';
})
.then(value => {
console.log('Etape 2:', value);
throw new Error('Erreur a l\'etape 2 !');
})
.then(value => {
// Cette etape est IGNOREE
console.log('Etape 3:', value);
})
.then(value => {
// Cette etape aussi est IGNOREE
console.log('Etape 4:', value);
})
.catch(error => {
console.error('Erreur capturee:', error.message);
return 'recuperation'; // La chaine peut continuer
})
.then(value => {
console.log('Etape finale:', value); // "recuperation"
});
Methodes Statiques Avancees
Promise.all() - Parallelisme Total
Execute plusieurs Promises en parallele et attend que toutes soient resolues :
const promise1 = fetch('/api/users');
const promise2 = fetch('/api/posts');
const promise3 = fetch('/api/comments');
Promise.all([promise1, promise2, promise3])
.then(([usersRes, postsRes, commentsRes]) => {
// Les trois requetes sont terminees
return Promise.all([
usersRes.json(),
postsRes.json(),
commentsRes.json()
]);
})
.then(([users, posts, comments]) => {
console.log('Utilisateurs:', users.length);
console.log('Posts:', posts.length);
console.log('Commentaires:', comments.length);
})
.catch(error => {
// Si UNE SEULE Promise echoue, tout echoue
console.error('Une requete a echoue:', error);
});
Comportement de Promise.all() :
- Resout avec un tableau de toutes les valeurs (dans l’ordre)
- Rejette des qu’UNE Promise echoue (fail-fast)
- Les autres Promises continuent mais leurs resultats sont ignores
Promise.race() - Le Premier Gagne
Retourne le resultat de la premiere Promise a se regler (fulfilled ou rejected) :
// Exemple : timeout pour une requete
function fetchWithTimeout(url, timeout = 5000) {
return Promise.race([
fetch(url),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout !')), timeout)
)
]);
}
fetchWithTimeout('/api/slow-endpoint', 3000)
.then(response => console.log('Reponse recue !'))
.catch(error => console.error(error.message)); // "Timeout !" si > 3s
Promise.allSettled() - Attendre Tout le Monde
Attend que toutes les Promises soient reglees, sans echouer si certaines sont rejetees :
const promises = [
fetch('/api/users'),
fetch('/api/nonexistent'), // Cette URL n'existe pas
fetch('/api/posts')
];
Promise.allSettled(promises)
.then(results => {
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Promise ${index}: Succes`, result.value);
} else {
console.log(`Promise ${index}: Echec`, result.reason);
}
});
});
// Sortie :
// Promise 0: Succes Response {...}
// Promise 1: Echec Error: 404
// Promise 2: Succes Response {...}
Cas d’usage : Quand vous voulez tenter plusieurs operations et traiter les resultats individuellement, meme si certaines echouent.
Promise.any() - Le Premier Succes
Retourne la premiere Promise a etre fulfilled (ignore les rejets) :
// Exemple : essayer plusieurs serveurs miroirs
const mirrors = [
'https://mirror1.example.com/file.zip',
'https://mirror2.example.com/file.zip',
'https://mirror3.example.com/file.zip'
];
Promise.any(mirrors.map(url => fetch(url)))
.then(response => {
console.log('Telechargement depuis:', response.url);
})
.catch(error => {
// AggregateError si TOUTES les Promises ont echoue
console.error('Tous les miroirs ont echoue');
console.error(error.errors); // Tableau de toutes les erreurs
});
Tableau Comparatif des Methodes Statiques
| Methode | Resout quand… | Rejette quand… |
|---|---|---|
Promise.all() | Toutes fulfilled | Une seule rejected |
Promise.race() | Premiere settled | Premiere rejected |
Promise.allSettled() | Toutes settled | Jamais |
Promise.any() | Premiere fulfilled | Toutes rejected |
Bonnes Pratiques
1. Toujours Retourner dans les Callbacks
// MAUVAIS - la Promise n'est pas chainee
promise.then(value => {
anotherPromise(value); // Oubli du return !
});
// BON - la Promise est correctement chainee
promise.then(value => {
return anotherPromise(value);
});
// ENCORE MIEUX - syntaxe fleche implicite
promise.then(value => anotherPromise(value));
2. Toujours Gerer les Erreurs
// MAUVAIS - erreur silencieuse
fetch('/api/data')
.then(response => response.json());
// BON - erreur geree
fetch('/api/data')
.then(response => response.json())
.catch(error => {
console.error('Erreur:', error);
// Notifier l'utilisateur, logger, etc.
});
3. Eviter l’Anti-Pattern Promise Constructor
// MAUVAIS - Promise constructor inutile
function getData() {
return new Promise((resolve, reject) => {
fetch('/api/data')
.then(response => resolve(response.json()))
.catch(error => reject(error));
});
}
// BON - retourner directement la Promise
function getData() {
return fetch('/api/data').then(response => response.json());
}
4. Utiliser async/await pour la Lisibilite
// Avec Promises chainee
function getFullUserData(userId) {
return getUser(userId)
.then(user => getOrders(user.id).then(orders => ({ user, orders })))
.then(({ user, orders }) =>
getOrderDetails(orders[0].id).then(details => ({ user, orders, details }))
);
}
// Avec async/await - beaucoup plus lisible
async function getFullUserData(userId) {
const user = await getUser(userId);
const orders = await getOrders(user.id);
const details = await getOrderDetails(orders[0].id);
return { user, orders, details };
}
5. Paralleliser quand c’est Possible
// LENT - sequentiel (inutilement)
async function loadData() {
const users = await fetch('/api/users');
const posts = await fetch('/api/posts');
const comments = await fetch('/api/comments');
// Temps total: T(users) + T(posts) + T(comments)
}
// RAPIDE - parallele
async function loadData() {
const [users, posts, comments] = await Promise.all([
fetch('/api/users'),
fetch('/api/posts'),
fetch('/api/comments')
]);
// Temps total: max(T(users), T(posts), T(comments))
}
Pieges Courants a Eviter
1. Oublier de Retourner la Promise
// BUG : la chaine est brisee
function process() {
return fetch('/api/data')
.then(response => {
response.json(); // OUBLI DU RETURN !
})
.then(data => {
console.log(data); // undefined !
});
}
2. Erreurs Avalees Silencieusement
// BUG : erreur jamais loggee
function fetchData() {
return fetch('/api/data')
.then(response => response.json())
.catch(error => {
// L'erreur est "avalee" - la Promise est maintenant fulfilled avec undefined
});
}
// CORRECTION
function fetchData() {
return fetch('/api/data')
.then(response => response.json())
.catch(error => {
console.error('Erreur:', error);
throw error; // Re-lancer pour permettre au code appelant de gerer
});
}
3. Creer des Promises Inutilement
// INUTILE - Promise deja une Promise
async function getData() {
return new Promise(resolve => {
resolve(fetch('/api/data')); // fetch retourne deja une Promise !
});
}
// CORRECT
async function getData() {
return fetch('/api/data');
}
4. Ignorer les Rejections non Gerees
// Provoque un UnhandledPromiseRejection
Promise.reject(new Error('Non geree'));
// Toujours gerer
Promise.reject(new Error('Geree'))
.catch(error => console.error(error));
5. forEach avec async ne Fonctionne pas comme Attendu
// BUG : forEach n'attend pas les Promises
async function processItems(items) {
items.forEach(async item => {
await processItem(item); // Ces awaits ne bloquent pas forEach !
});
console.log('Termine ?'); // S'affiche AVANT que tout soit traite !
}
// CORRECTION avec for...of
async function processItems(items) {
for (const item of items) {
await processItem(item);
}
console.log('Vraiment termine !');
}
// OU en parallele avec Promise.all
async function processItems(items) {
await Promise.all(items.map(item => processItem(item)));
console.log('Termine en parallele !');
}
Conclusion
Les Promises ont revolutionne la programmation asynchrone en JavaScript. Elles offrent :
- Une API standardisee pour gerer les operations asynchrones
- Un chainage elegant qui elimine le callback hell
- Une gestion d’erreurs centralisee avec propagation automatique
- Des methodes puissantes (all, race, allSettled, any) pour orchestrer plusieurs operations
Recapitulatif : Callbacks vs Promises vs Async/Await
| Aspect | Callbacks | Promises | Async/Await |
|---|---|---|---|
| Lisibilite | Faible (pyramid of doom) | Moyenne (chainage) | Excellente (synchrone) |
| Gestion erreurs | Fragmentee | Centralisee (catch) | try/catch natif |
| Parallelisme | Complexe | Promise.all() | await Promise.all() |
| Debug | Difficile | Moyen | Facile (stack traces) |
| Support navigateur | Universel | ES6+ | ES2017+ |
Conseil final : Utilisez async/await pour le code metier (meilleure lisibilite) et les Promises brutes pour les utilitaires et bibliotheques. Maitrisez les deux, car async/await n’est qu’une surcouche syntaxique des Promises.
Vous etes maintenant armes pour ecrire du code asynchrone elegant, maintenable et robuste !
In-Article Ad
Dev Mode
Mahmoud DEVO
Senior Full-Stack Developer
I'm a passionate full-stack developer with 10+ years of experience building scalable web applications. I write about Vue.js, Node.js, PostgreSQL, and modern DevOps practices.
Enjoyed this article?
Subscribe to get more tech content delivered to your inbox.
Related Articles
Maitriser async/await en JavaScript : iterateurs asynchrones et Promise.all
Guide complet sur async/await en JavaScript : fonctions asynchrones, iterateurs, Promise.all et pieges a eviter pour un code performant.
Fonctions flechees et WebSockets en JavaScript : guide complet avec exemples pratiques
Apprenez a utiliser les fonctions flechees et WebSockets en JavaScript. Connexions bidirectionnelles, messages binaires et securite HTTPS.
Propriétés et Descripteurs en JavaScript : Comprendre les pr
Voici une proposition de meta description : "Découvrez comment les propriétés et descripteurs en JavaScript fonctionnent réellement ! Comprenons l'utilisation