
Ecriture de fichiers (synchrone et asynchrone)
Apprenez a ecrire des donnees dans des fichiers avec Node.js via les methodes asynchrones (writeFile, promises.writeFile) et synchrones (writeFileSync) du module fs.
Sauvegarder des donnees : l'ecriture de fichiers
Tout comme la lecture, l'écriture de données dans des fichiers est une tâche essentielle pour de nombreuses applications Node.js. Que ce soit pour enregistrer des données de configuration générées, sauvegarder des informations de session, créer des fichiers de logs, générer des rapports, ou stocker le résultat d'un traitement, la capacité à écrire sur le système de fichiers est indispensable.
Le module `fs` propose, de manière symétrique à la lecture, des fonctions pour écrire dans des fichiers, disponibles en versions asynchrones (non bloquantes) et synchrones (bloquantes). Encore une fois, la préférence ira très nettement aux méthodes asynchrones dans un contexte serveur pour préserver la réactivité de l'application.
Ecriture asynchrone : `fs.writeFile` et `fs.promises.writeFile`
La méthode standard et recommandée pour écrire des données dans un fichier de manière asynchrone est `fs.writeFile()`. Si le fichier spécifié existe déjà, son contenu sera par défaut écrasé. Si le fichier n'existe pas, il sera créé.
Sa signature de base avec callback est :
fs.writeFile(file, data, [options], callback)file: Le chemin du fichier de destination (chaîne, Buffer, URL `file:`). Si le répertoire parent n'existe pas, l'opération échouera.data: Les données à écrire (chaîne de caractères, Buffer, TypedArray, DataView, ou objet itérable asynchrone/synchrone).options(optionnel) : Un objet ou une chaîne spécifiant l'encodage (par défaut `'utf8'` pour les chaînes), le mode (permissions, par défaut `0o666`) et le flag (par défaut `'w'` pour écraser/créer). Pour ajouter au fichier existant, utilisez `{ flag: 'a' }`.callback: Une fonction appelée une fois l'écriture terminée ou en cas d'erreur. Elle reçoit un unique argument : `(err)`. Si l'écriture réussit, `err` est `null`. Sinon, il contient l'objet d'erreur (`EACCES` pour permissions, `ENOENT` pour répertoire inexistant, `ENOSPC` pour disque plein, etc.).
Exemple avec callback :
const fs = require('fs');
const userConfig = {
username: 'Alice',
theme: 'dark',
notifications: true
};
const dataToWrite = JSON.stringify(userConfig, null, 2); // Formatter le JSON pour lisibilité
fs.writeFile('user_settings.json', dataToWrite, 'utf8', (err) => {
if (err) {
console.error('Erreur lors de l\'écriture du fichier:', err);
return;
}
console.log('Fichier de configuration sauvegardé avec succès !');
});
console.log('Demande d\'écriture initiée...'); // S'affiche avant la fin de l'écritureLa version basée sur les Promesses, `fs.promises.writeFile()`, est souvent préférée pour sa meilleure intégration avec `async/await` :
const fs = require('fs').promises;
const reportData = 'Rapport généré le ' + new Date().toISOString();
async function saveReport(filePath, data) {
try {
await fs.writeFile(filePath, data, {
encoding: 'utf8',
flag: 'a' // 'a' pour append (ajouter à la fin)
});
console.log(`Données ajoutées avec succès au fichier ${filePath}`);
} catch (err) {
console.error(`Erreur lors de l'ajout au fichier ${filePath}:`, err);
}
}
console.log('Appel de saveReport...');
saveReport('activity_log.txt', reportData + '\n');
console.log('Demande d\'écriture (async/await) initiée...');Avantages de l'asynchrone : N'interrompt pas le flux d'exécution de l'application, crucial pour maintenir la performance et la capacité à gérer des opérations concurrentes.
Ecriture synchrone : `fs.writeFileSync` (utilisation limitee)
Le pendant synchrone est `fs.writeFileSync()`. Tout comme `readFileSync`, cette fonction bloque l'intégralité du processus Node.js jusqu'à ce que l'écriture sur le disque soit terminée.
Sa signature :
fs.writeFileSync(file, data, [options])file,data,options: Identiques à `fs.writeFile`.- Comportement : La fonction ne retourne rien si l'écriture réussit.
- Erreurs : En cas d'erreur durant l'écriture, elle lance une exception. Un bloc `try...catch` est donc indispensable pour gérer les erreurs potentielles.
Exemple d'utilisation (à utiliser avec parcimonie) :
const fs = require('fs');
const initialConfig = '// Fichier de config initial - Do not edit manually\n' + JSON.stringify({ version: '1.0' });
console.log('Tentative d\'écriture synchrone...');
try {
// L'exécution est bloquée ici
fs.writeFileSync('app_init_config.js', initialConfig, 'utf8');
console.log('Fichier de configuration initial créé (synchrone).');
} catch (err) {
console.error('Erreur lors de l\'écriture synchrone:', err);
}
console.log('Ecriture synchrone terminée (ou erreur capturée).');Quand utiliser `writeFileSync` ? Comme pour la lecture synchrone, son usage doit être restreint aux phases d'initialisation d'une application (avant le démarrage du serveur) ou à des scripts utilitaires simples qui ne nécessitent pas de gérer la concurrence. Son utilisation dans une fonction gérant une requête web est une très mauvaise pratique car elle bloquera le serveur, empêchant le traitement d'autres requêtes.
Ajouter des donnees : `fs.appendFile` et l'option `flag: 'a'`
Si l'objectif n'est pas d'écraser le fichier mais d'ajouter des données à la fin, deux approches principales existent :
- Utiliser la fonction dédiée `fs.appendFile()` (ou `fs.promises.appendFile()` / `fs.appendFileSync()`). Elle prend les mêmes arguments que `writeFile` mais ajoute systématiquement les données à la fin du fichier (le créant s'il n'existe pas).
- Utiliser `fs.writeFile()` (ou ses variantes) en spécifiant l'option `{ flag: 'a' }`. Le flag `'a'` signifie 'append'.
Exemple avec `appendFile` (asynchrone, promesse) :
const fs = require('fs').promises;
async function logEvent(message) {
const timestamp = new Date().toISOString();
const logEntry = `${timestamp} - ${message}\n`;
try {
await fs.appendFile('application.log', logEntry, 'utf8');
} catch (err) {
console.error('Impossible d\'écrire dans le fichier log:', err);
}
}
logEvent('Service démarré.');
logEvent('Connexion utilisateur réussie.');L'utilisation de `appendFile` ou du flag `'a'` est typique pour les fichiers de logs ou toute situation où l'historique doit être préservé.
Considerations importantes et alternatives
Encodage : Si `data` est une chaîne, `'utf8'` est l'encodage par défaut. Si c'est un Buffer, les octets sont écrits tels quels.
Gestion des erreurs : Toujours prévoir un traitement des erreurs (`err` dans le callback, `catch` pour les promesses et les fonctions synchrones). Les erreurs d'écriture peuvent survenir pour diverses raisons (permissions, espace disque, etc.).
Atomicité : Les opérations `fs.writeFile` ne sont généralement pas atomiques. Si le processus Node.js ou le système s'arrête brutalement pendant une écriture, le fichier peut se retrouver dans un état corrompu ou incomplet. Pour des écritures atomiques (garantir que soit l'ancienne version, soit la nouvelle version complète existe), des stratégies plus complexes sont nécessaires (par exemple, écrire dans un fichier temporaire puis le renommer atomiquement).
Fichiers volumineux et écritures fréquentes : Tout comme pour la lecture, `fs.writeFile` charge l'intégralité des `data` en mémoire avant de commencer l'écriture. Pour écrire de gros volumes de données ou pour des opérations d'écriture très fréquentes (comme du logging intensif), cette approche est inefficace. Il est fortement recommandé d'utiliser les Streams avec `fs.createWriteStream`. Les streams écrivent les données par morceaux dès qu'elles sont disponibles, consommant beaucoup moins de mémoire et offrant de meilleures performances, notamment grâce à la gestion de la contre-pression.