Pourquoi Promises en JavaScript ?
Promises sont un élément crucial en JavaScript pour gérer les opérations asynchrones de manière plus efficace et intuitive. Dans une application moderne, beaucoup d'opérations se déroulent en arrière-plan, comme des appels à l'API, la lecture/écriture de fichiers, ou des requêtes réseau. Sans Promises, les développeurs auraient du recourir à des callbacks imbriqués (callback hell), ce qui rend le code difficile à lire et à maintenir.
Un cas d'usage concret est lorsqu'on souhaite récupérer des données depuis une API RESTful pour mettre à jour l'état d'une application. Sans Promises, on pourrait avoir quelque chose comme ceci :
// Callback hell : une API de données fictive
function fetchData(callback) {
setTimeout(() => {
callback(null, { data: 'Données récupérées' });
}, 2000);
}
fetchData((error, result) => {
if (error) {
console.error(error);
} else {
console.log(result.data);
}
});
Ce code est difficile à lire et maintenir, surtout s'il y a plusieurs niveaux de callbacks imbriqués. C'est là que entrent en jeu les Promises.
Prerequis
- Connaissance de base de JavaScript (variables, fonctions, boucles, etc.)
- Compréhension des objets et des fonctions fléchées
- Familiarité avec le concept de
setTimeout,setInterval, etc.
Outils à installer (versions)
- Node.js (v12 ou plus récent)
- Un éditeur de code moderne comme VSCode
Concepts fondamentaux
1. Ce qu'est une Promise ?
Une Promise est un objet qui représente l'achèvement d'une opération asynchrone et sa valeur finale, soit la réussite, soit l'échec.
// Création d'une Promise
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Opération réussie');
}, 2000);
});
myPromise.then(result => {
console.log(result); // 'Opération réussie'
}).catch(error => {
console.error(error);
});
2. La méthode then()
La méthode then() est utilisée pour traiter la valeur d'une Promise résolue.
// Utilisation de then()
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Opération réussie');
}, 2000);
});
myPromise.then(result => {
console.log(result); // 'Opération réussie'
});
3. La méthode catch()
La méthode catch() est utilisée pour gérer les erreurs de la Promise.
// Utilisation de catch()
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
reject('Opération échouée');
}, 2000);
});
myPromise.then(result => {
console.log(result);
}).catch(error => {
console.error(error); // 'Opération échouée'
});
4. La méthode finally()
La méthode finally() est utilisée pour exécuter un code qui sera toujours exécuté, quel que soit le résultat de la Promise.
// Utilisation de finally()
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Opération réussie');
}, 2000);
});
myPromise.then(result => {
console.log(result);
}).catch(error => {
console.error(error);
}).finally(() => {
console.log('Opération terminée'); // Toujours exécuté
});
Mise en pratique : Projet fil rouge
Nous allons construire un petit gestionnaire de tâches asynchrone avec Promises. Voici le projet complet et fonctionnel :
Étape 1 : Création du fichier index.js
// index.js
const fs = require('fs').promises;
async function main() {
try {
// Lire les tâches depuis un fichier JSON
const data = await fs.readFile('tasks.json', 'utf8');
let tasks = JSON.parse(data);
// Ajouter une nouvelle tâche
const newTask = { id: Date.now(), title: 'Nouvelle tâche', completed: false };
tasks.push(newTask);
// Écrire les tâches à nouveau dans le fichier
await fs.writeFile('tasks.json', JSON.stringify(tasks, null, 2));
console.log('Tâche ajoutée avec succès');
// Lire et afficher toutes les tâches
const updatedData = await fs.readFile('tasks.json', 'utf8');
tasks = JSON.parse(updatedData);
console.log('Liste des tâches :', tasks);
} catch (error) {
console.error('Erreur lors de l\'opération sur les tâches:', error);
}
}
main();
Étape 2 : Création du fichier tasks.json
// tasks.json
[]
Étape 3 : Exécution du script
node index.js
Ce script lit un fichier JSON contenant des tâches, ajoute une nouvelle tâche, et enregistre les modifications dans le même fichier.
Erreurs fréquentes et debugging
1. Erreur : TypeError: Cannot read property 'then' of undefined
Code incorrect :
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Opération réussie');
}, 2000);
});
myPromise.then(result => {
console.log(result);
});
Code correct :
Le code ci-dessus est correct et ne devrait pas générer d'erreur.
2. Erreur : SyntaxError: Unexpected token } in JSON at position 1
Code incorrect :
const data = await fs.readFile('tasks.json', 'utf8');
let tasks = JSON.parse(data);
Code correct :
const data = await fs.readFile('tasks.json', 'utf8');
let tasks = JSON.parse(data.trim()); // Ajout de trim()
3. Erreur : UnhandledPromiseRejectionWarning: Error: ENOENT: no such file or directory, open 'tasks.json'
Code incorrect :
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Opération réussie');
}, 2000);
});
myPromise.then(result => {
console.log(result);
});
Code correct :
Ajouter une gestion des erreurs de lecture du fichier :
const fs = require('fs').promises;
async function main() {
try {
const data = await fs.readFile('tasks.json', 'utf8');
let tasks = JSON.parse(data);
console.log(tasks);
} catch (error) {
if (error.code === 'ENOENT') {
console.log('Le fichier n\'existe pas, on le crée.');
await fs.writeFile('tasks.json', '[]');
} else {
console.error('Erreur lors de la lecture du fichier :', error);
}
}
}
main();
Pour aller plus loin
1. Async/Await
Async/Await est une syntaxe simplifiée pour travailler avec des Promises.
// Utilisation d'async/await
const fetchData = async () => {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Erreur lors de la récupération des données :', error);
}
};
fetchData();
2. Chaining Promises
La chaîne des Promises permet d'effectuer une série d'opérations asynchrones en chaînant les méthodes then().
// Chaînage de Promises
const fetchData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Données récupérées');
}, 2000);
});
};
fetchData()
.then(result => {
console.log(result);
return fetchData();
})
.then(secondResult => {
console.log(secondResult);
})
.catch(error => {
console.error(error);
});
3. Promise.all()
La méthode Promise.all() permet d'exécuter plusieurs Promises simultanément et de traiter les résultats une fois toutes les Promises résolues.
// Utilisation de Promise.all()
const fetchUser = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Utilisateur récupéré');
}, 1000);
});
};
const fetchPosts = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Posts récupérés');
}, 2000);
});
};
Promise.all([fetchUser(), fetchPosts()])
.then(results => {
console.log(results); // ['Utilisateur récupéré', 'Posts récupérés']
})
.catch(error => {
console.error(error);
});
Défi pratique
Créez une application CLI pour gérer des notes. L'application devrait permettre d'ajouter, de lire et de supprimer des notes en utilisant des Promises.
node app.js add "Ma première note"
node app.js list
node app.js delete 1
Le code de l'application doit être complet et fonctionnel.