Menu

Node.js fs/promises: Dateisystem-API mit async/await

fs/promises ist die Promise-Variante des fs-Moduls von Node: readFile, writeFile, readdir, stat, mkdir, rm und rename geben alle Promises zurück, auf die du mit await wartest, und Fehler fängst du mit try/catch.

Diese Seite enthält ausführbare Editoren. Bearbeiten, ausführen und Ausgabe sofort sehen.

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.

FunktionMachtWird aufgelöst zu
readFile(path, 'utf8')Liest eine ganze DateiDem Inhalt
writeFile(path, data)Legt eine Datei an oder ersetzt sieundefined
appendFile(path, data)Hängt ans Ende anundefined
readdir(path)Listet einen Ordner aufEinem Array von Namen
stat(path)Größe, Datum, Datei oder OrdnerEinem Stats-Objekt
mkdir(path, { recursive: true })Legt Ordner anDem ersten angelegten Ordner oder undefined
rename(from, to)Verschiebt oder benennt umundefined
rm(path, { recursive: true, force: true })Löscht eine Datei oder einen Ordnerundefined

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.

Illustration der Programmiersprachen bei Coddy

Lerne mit Coddy zu programmieren

Los geht's