fs/promises è la versione a promise del modulo fs di Node. Le sue funzioni (readFile, writeFile, readdir, stat, mkdir, rm, rename e le altre) restituiscono una promise invece di ricevere una callback, quindi fai await e gestisci gli errori con try/catch. Importalo con require('fs/promises') o import fs from 'fs/promises'.
Stampa [ 'write docs', 'ship it' ]: ogni await aspetta un'operazione prima che venga eseguita la riga successiva, quindi l'append avviene sempre dopo la prima scrittura. In un modulo ES await funziona al livello principale; in CommonJS, metti il codice dentro una funzione async.
Le funzioni che userai di più
I nomi sono quelli dell'API a callback, senza la callback. Le pagine su readFile e writeFile trattano queste due nel dettaglio.
| Funzione | Cosa fa | Si risolve in |
|---|---|---|
readFile(path, 'utf8') | Legge un file intero | Il contenuto |
writeFile(path, data) | Crea o sostituisce un file | undefined |
appendFile(path, data) | Aggiunge in fondo | undefined |
readdir(path) | Elenca una cartella | Un array di nomi |
stat(path) | Dimensione, date, file o cartella | Un oggetto Stats |
mkdir(path, { recursive: true }) | Crea cartelle | La prima cartella creata, o undefined |
rename(from, to) | Sposta o rinomina | undefined |
rm(path, { recursive: true, force: true }) | Elimina un file o una cartella | undefined |
Elencare una cartella con readdir e stat
readdir restituisce solo i nomi. Chiama stat su ognuno per sapere la dimensione o distinguere file e cartelle, oppure passa { withFileTypes: true } per avere voci che conoscono già il loro tipo.
Il ciclo stampa README.md 6 bytes, notes.txt 3 bytes e src folder. stat viene rifiutata con ENOENT per un path che non esiste, quindi è un controllo di esistenza rapido quando ti servono anche i dettagli.
Rinominare, spostare ed eliminare
rename rinomina e sposta un file (sullo stesso disco). rm elimina; serve recursive: true per una cartella con dei contenuti, e force: true per non fallire quando il path non c'è già più.
Il file passa da drafts a published con il nuovo nome, e dopo rm la cartella blog contiene solo published. Togli force: true dal secondo rm e la promise viene rifiutata con ENOENT.
Gestire gli errori con try/catch
Un'operazione che fallisce rifiuta la sua promise. Con await quel rifiuto diventa un'eccezione da intercettare come tutte le altre. Il code dell'errore ti dice cosa è andato storto.
La prima chiamata non trova il file e restituisce []; la seconda stampa Ada. I codici da conoscere: ENOENT (file inesistente), EEXIST (esiste già), EISDIR (una cartella dove ci si aspettava un file), ENOTEMPTY (una cartella ha ancora dei contenuti) ed EACCES (permesso negato).
Una promise su cui non fai await e senza .catch() diventa un unhandled rejection quando fallisce, e Node termina con un errore. Fai await di ogni chiamata, oppure restituisci la promise a un chiamante che lo faccia.
Leggere più file insieme con Promise.all
Un ciclo for con await legge i file uno dopo l'altro. Quando le letture non dipendono l'una dall'altra, avviale tutte e aspetta una volta sola con Promise.all: il disco ci lavora in contemporanea.
Promise.all mantiene l'ordine dell'array di partenza, quindi il risultato è [ 'alpha', 'beta', 'gamma' ] indipendentemente da quale lettura è finita prima, e la lunghezza unita è 14. Rinomina 'b.txt' in 'x.txt' nell'array e tutto l'await viene rifiutato con ENOENT.
Se ti servono tutti i risultati anche quando alcune letture falliscono, usa Promise.allSettled; la pagina sulle promise le spiega entrambe.
Errori comuni
Confondere i due moduli. require('fs').readFile vuole una callback; require('fs/promises').readFile restituisce una promise. Chiamare la versione a callback senza callback lancia The "cb" argument must be of type function.
Dimenticare await. const text = fs.readFile(...) è una promise in sospeso, e text.split lancia text.split is not a function.
Scritture parallele sullo stesso file. Promise.all con più scritture sullo stesso path può lasciare contenuti mescolati. Eseguile in sequenza.
Domande frequenti
Che differenza c'è tra fs.promises e fs/promises?
Nessuna. require('fs').promises e require('fs/promises') sono lo stesso oggetto. Il path fs/promises (o node:fs/promises) è il modo più comune di importarlo oggi.
Esiste un existsSync in fs/promises?
No. L'API a promise ha access, che viene rifiutata quando il path non esiste, ma di solito si chiama direttamente l'operazione che serve e si intercetta ENOENT. existsSync del modulo fs normale funziona ancora per controlli veloci.
fs/promises è più lento dell'API a callback?
Un po', per il costo delle promise, ma è trascurabile rispetto ai tempi del disco. Scegli in base alla leggibilità; l'API a promise è quella usata dalla maggior parte del codice nuovo.
Come trasformo una funzione fs a callback in una promise?
Raramente serve, perché fs/promises copre le funzioni sui file. Per altre API a callback, util.promisify(fn) restituisce una versione che ritorna una promise.