fs/promises ist die Promise-basierte Variante des fs-Moduls von Node. Seine Funktionen (readFile, writeFile, readdir, stat, mkdir, rm, rename und die übrigen) geben ein Promise zurück, statt einen Callback zu nehmen, du wartest also mit await auf sie und fängst Fehler mit try/catch. Importier es mit require('fs/promises') oder import fs from 'fs/promises'.
Es gibt [ 'write docs', 'ship it' ] aus: Jedes await wartet auf eine Operation, bevor die nächste Zeile läuft, das Anhängen passiert also immer nach dem ersten Schreiben. In einem ES-Modul funktioniert await auf oberster Ebene; in CommonJS packst du den Code in eine async-Funktion.
Die Funktionen, die du am häufigsten brauchst
Die Namen entsprechen der Callback-API, nur ohne Callback. Die Seiten zu readFile und writeFile behandeln diese beiden im Detail.
| Funktion | Macht | Wird aufgelöst zu |
|---|---|---|
readFile(path, 'utf8') | Liest eine ganze Datei | Dem Inhalt |
writeFile(path, data) | Legt eine Datei an oder ersetzt sie | undefined |
appendFile(path, data) | Hängt ans Ende an | undefined |
readdir(path) | Listet einen Ordner auf | Einem Array von Namen |
stat(path) | Größe, Datum, Datei oder Ordner | Einem Stats-Objekt |
mkdir(path, { recursive: true }) | Legt Ordner an | Dem ersten angelegten Ordner oder undefined |
rename(from, to) | Verschiebt oder benennt um | undefined |
rm(path, { recursive: true, force: true }) | Löscht eine Datei oder einen Ordner | undefined |
Einen Ordner mit readdir und stat auflisten
readdir gibt nur Namen zurück. Ruf für jeden stat auf, um seine Größe zu bekommen oder Dateien von Ordnern zu unterscheiden, oder übergib { withFileTypes: true }, um Einträge zu bekommen, die ihren Typ schon kennen.
Die Schleife gibt README.md 6 bytes, notes.txt 3 bytes und src folder aus. stat wird für einen Pfad, der nicht existiert, mit ENOENT abgelehnt und ist damit eine schnelle Existenzprüfung, wenn du auch die Details willst.
Umbenennen, verschieben und löschen
rename benennt eine Datei um und verschiebt sie auch (innerhalb desselben Laufwerks). rm löscht; für einen Ordner mit Inhalt braucht es recursive: true und force: true, damit es nicht fehlschlägt, wenn der Pfad schon weg ist.
Die Datei wandert unter ihrem neuen Namen von drafts nach published, und nach rm listet der Ordner blog nur noch published. Entferne force: true aus dem zweiten rm, und es wird mit ENOENT abgelehnt.
Fehler mit try/catch behandeln
Eine fehlgeschlagene Operation lehnt ihr Promise ab. Mit await wird diese Ablehnung zu einer Exception, die du wie jede andere fängst. Der code des Fehlers sagt dir, was schiefging.
Der erste Aufruf findet keine Datei und gibt [] zurück; der zweite gibt Ada aus. Codes, die du kennen solltest: ENOENT (Datei existiert nicht), EEXIST (existiert schon), EISDIR (ein Ordner, wo eine Datei erwartet wurde), ENOTEMPTY (ein Ordner hat noch Inhalt) und EACCES (keine Berechtigung).
Ein Promise, auf das du weder mit await wartest noch .catch() anhängst, wird bei einem Fehler zu einer unbehandelten Ablehnung, und Node beendet sich mit einem Fehler. Warte auf jeden Aufruf oder gib sein Promise an einen Aufrufer zurück, der das tut.
Mehrere Dateien gleichzeitig mit Promise.all lesen
Eine for-Schleife mit await liest Dateien nacheinander. Wenn die Lesevorgänge nicht voneinander abhängen, starte alle und warte einmal mit Promise.all: Die Festplatte arbeitet gleichzeitig daran.
Promise.all behält die Reihenfolge des Eingabe-Arrays bei, das Ergebnis ist also [ 'alpha', 'beta', 'gamma' ], egal welcher Lesevorgang zuerst fertig war, und die zusammengefügte Länge ist 14. Benenn 'b.txt' im Array in 'x.txt' um, und das ganze await wird mit ENOENT abgelehnt.
Wenn du jedes Ergebnis brauchst, auch wenn manche Lesevorgänge fehlschlagen, nimm Promise.allSettled; die Seite zu Promises behandelt beide.
Häufige Fehler
Die beiden Module verwechseln. require('fs').readFile will einen Callback; require('fs/promises').readFile gibt ein Promise zurück. Die Callback-Variante ohne Callback aufzurufen, wirft The "cb" argument must be of type function.
await vergessen. const text = fs.readFile(...) ist ein ausstehendes Promise, und text.split wirft text.split is not a function.
Parallele Schreibvorgänge in dieselbe Datei. Promise.all mit mehreren Schreibvorgängen auf denselben Pfad kann vermischten Inhalt hinterlassen. Führ diese nacheinander aus.
Häufig gestellte Fragen
Was ist der Unterschied zwischen fs.promises und fs/promises?
Keiner. require('fs').promises und require('fs/promises') sind dasselbe Objekt. Der Pfad fs/promises (oder node:fs/promises) ist heute der übliche Import.
Gibt es in fs/promises ein existsSync?
Nein. Die Promise-API hat access, das abgelehnt wird, wenn der Pfad fehlt, aber üblich ist, die benötigte Operation direkt aufzurufen und ENOENT zu fangen. existsSync aus dem normalen fs-Modul funktioniert für schnelle Prüfungen weiterhin.
Ist fs/promises langsamer als die Callback-API?
Ein wenig, wegen des Overheads der Promises, aber neben der Zeit für die Festplatte ist das vernachlässigbar. Entscheide nach Lesbarkeit; die Promise-API ist die, die der meiste neue Code nutzt.
Wie mache ich aus einer fs-Funktion mit Callback ein Promise?
Das brauchst du selten, weil fs/promises die Dateifunktionen abdeckt. Für andere Callback-APIs gibt util.promisify(fn) eine Variante zurück, die ein Promise liefert.