Zum Inhalt springen
aviral gupta

// B3.2 · ca. 34 Min. · Einstieg

Dateien lesen mit fs/promises

Nach dieser Lektion lesen Sie Text- und JSON-Dateien, unterscheiden eine fehlende Datei von einem Ordner oder einer gesperrten Datei und finden eine Datei neben Ihrem Modul.

Lektion 2 von 5 in B3 Dateien und Pfade

Danach können Sie

  • Eine Datei als Buffer oder String lesen und ihren Text in JSON oder Zeilen umwandeln
  • ENOENT, EISDIR und EACCES per error.code behandeln und den Rest weiterwerfen
  • Promise-, Callback- und Sync-Form vergleichen und Pfade relativ zu import.meta.dirname bilden
  1. Aufwärmen · Aufgabe 1 von 7

    Aufwärmen aus B1.1: Welches Modul liefert ein readFile, das ein Promise zurückgibt, sodass Sie es mit await abwarten können?

  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie es vorher, bevor Sie weiterlesen. hello.txt enthält die Zeile Hi. Was gibt dieses Programm aus?

    import {readFile} from 'node:fs/promises';
    
    const data = await readFile('hello.txt');
    console.log(data);
  3. Üben · Aufgabe 3 von 7

    Setzen Sie die Kodierung ein, damit readFile einen String liefert und das Programm string buy milk ausgibt.

    const text = await readFile("notes.txt", "____");
    console.log(typeof text, text.trim());
    const text = await readFile("notes.txt", "");
  4. Üben · Aufgabe 4 von 7

    Ihr Programm liest eine Datei und wertet sie als JSON aus. Ordnen Sie jedem Fehler die Situation zu, die ihn auslöst.

  5. Üben · Aufgabe 5 von 7

    In welcher Reihenfolge gibt dieses Programm seine drei Zeilen aus?

    import {readFileSync} from 'node:fs';
    import {readFile} from 'node:fs/promises';
    
    readFile('names.txt', 'utf8').then(() => console.log('promise'));
    readFileSync('names.txt', 'utf8');
    console.log('sync');
    console.log('end');
  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. Der Ordner app enthält main.js und data.txt mit dem Inhalt hello from app. Sie stehen im Ordner über app und führen node app/main.js aus. Was passiert?

    // app/main.js
    import {readFile} from 'node:fs/promises';
    
    const text = await readFile('data.txt', 'utf8');
    console.log(text.trim());
  7. Anwenden · Aufgabe 7 von 7

    Mini-Aufgabe. Legen Sie einen Ordner app mit config.json ({"port": 8080}) und einer main.js an, die config.json neben sich liest und port 8080 ausgibt. Starten Sie sie aus dem Ordner darüber mit node app/main.js. Benennen Sie config.json dann um und ersetzen Sie sie später durch einen gleichnamigen Ordner: Jedes Mal muss das Programm eine klare Zeile auf stderr ausgeben und mit Exit-Code 1 enden, ohne Stacktrace.

    Prüfen Sie Ihr Ergebnis anhand dieser Liste

Selbst programmieren

Lesen Sie das ausgearbeitete Beispiel und lösen Sie dann die Übungen. Ihr Code läuft in Ihrem Browser oder auf Ihrem Computer und wird nie hochgeladen.

Ausgearbeitetes Beispiel

Eine Aufgabenliste und drei Arten, wie Lesen scheitern kann

Dieses Programm liest tasks.txt aus dem Ordner von main.js, zerlegt sie in Zeilen und überspringt die leere. Dann versucht es eine Datei, die es nicht gibt, und den Ordner selbst und macht aus jedem error.code einen kurzen Satz; ein unerwarteter Code würde weitergeworfen. Zuletzt liest es tasks.txt ohne Kodierung, um zu zeigen, dass Sie dann Bytes erhalten. Weil die Pfade bei import.meta.dirname beginnen, ist die Ausgabe gleich, egal von wo Sie es starten.

main.js

import {readFile} from 'node:fs/promises';
import {join} from 'node:path';

const here = import.meta.dirname; // the folder of main.js

async function show(name) {
  try {
    const text = await readFile(join(here, name), 'utf8');
    const lines = text.split(/\r?\n/).filter((line) => line.trim() !== '');
    console.log(`${name}: ${lines.length} tasks`);
    for (const line of lines) console.log('  -', line);
  } catch (error) {
    const why = {ENOENT: 'does not exist', EISDIR: 'is a folder', EACCES: 'may not be read'}[error.code];
    if (!why) throw error; // not one we expected: let it crash
    console.log(`${name}: ${why} (${error.code})`);
  }
}

await show('tasks.txt');
await show('missing.txt');
await show('.');

const bytes = await readFile(join(here, 'tasks.txt'));
console.log('without an encoding:', bytes.length, 'bytes', bytes.subarray(0, 3));

tasks.txt

buy milk

water plants
call Ada

Ausführen mit

node main.js

Ausgabe

tasks.txt: 3 tasks
  - buy milk
  - water plants
  - call Ada
missing.txt: does not exist (ENOENT)
.: is a folder (EISDIR)
without an encoding: 32 bytes <Buffer 62 75 79>
  • Die leere Zeile zwischen buy milk und water plants und das leere Element nach dem letzten Zeilenumbruch werden herausgefiltert.
  • join(here, '.') ist der Ordner von main.js selbst, also lehnt readFile mit EISDIR ab.
  • EACCES steht in der Liste, aber hier ist keine Datei gesperrt, also taucht es nie auf.
  • Ohne "utf8" liefert readFile einen Buffer: 62 75 79 sind die Bytes von b, u und y.

Übungen

Übung 1 von 2

Vom Dateitext zu Datensätzen

Ein Programm liest scores.txt mit readFile(path, "utf8") und übergibt den Text an parseScores(text). Jede Zeile lautet name,score. Geben Sie ein Array von {name, score}-Objekten zurück, mit score als Zahl. Überspringen Sie leere Zeilen, auch das leere Element nach dem letzten Zeilenumbruch, akzeptieren Sie Windows-Zeilenenden (\r\n) und entfernen Sie Leerzeichen um beide Teile. Dieser Teil braucht gar keine Datei, er läuft also auch im Browser.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser den JavaScript-Runner herunter (bis zu 0.1 MB) und speichert ihn im Cache. Ihr Code läuft in der eigenen Engine Ihres Browsers und bleibt auf Ihrem Gerät.

Hinweise
  1. Hinweis 1

    text.split(/\r?\n/) schneidet an \n und an \r\n gleichermaßen.

  2. Hinweis 2

    filter((line) => line.trim() !== "") entfernt leere Zeilen und das leere letzte Element.

  3. Hinweis 3

    line.split(",") ergibt [name, score]; kürzen Sie den Namen mit trim, und Number(" 90") ist 90.

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

// Turns the text of scores.txt (one "name,score" per line) into records.
// readFile(path, "utf8") gives this text; parsing it needs no file.
export function parseScores(text) {
  return text
    .split(/\r?\n/)
    .filter((line) => line.trim() !== "")
    .map((line) => {
      const [name, score] = line.split(",");
      return {name: name.trim(), score: Number(score)};
    });
}

console.log(parseScores("Ada,90\nGrace,85\n"));
Auf dem eigenen Computer ausführen

Installieren Sie Node.js 24 LTS oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.js

// Turns the text of scores.txt (one "name,score" per line) into records.
// readFile(path, "utf8") gives this text; parsing it needs no file.
export function parseScores(text) {
  return [];
}

console.log(parseScores("Ada,90\nGrace,85\n"));

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {parseScores} from './main.js';

test('zwei Zeilen mit abschließendem Zeilenumbruch ergeben zwei Datensätze', () => {
  const got = parseScores('Ada,90\nGrace,85\n');
  assert.deepEqual(got, [{name: 'Ada', score: 90}, {name: 'Grace', score: 85}], `parseScores lieferte ${JSON.stringify(got)}`);
});

test('leere Zeilen werden übersprungen', () => {
  const got = parseScores('Ada,90\n\n\nGrace,85');
  assert.equal(got.length, 2, `parseScores lieferte ${JSON.stringify(got)}`);
});

test('Windows-Zeilenenden und Leerzeichen werden behandelt', () => {
  const got = parseScores(' Ada , 90\r\nGrace,85\r\n');
  assert.deepEqual(got, [{name: 'Ada', score: 90}, {name: 'Grace', score: 85}], `parseScores lieferte ${JSON.stringify(got)}`);
});

test('eine leere Datei ergibt ein leeres Array', () => {
  assert.deepEqual(parseScores(''), [], `parseScores("") lieferte ${JSON.stringify(parseScores(''))}`);
});

package.json

{
  "type": "module"
}

package.json sagt Node.js, dass die .js-Dateien Module sind; lassen Sie die Datei im Ordner.

Programm ausführen:

node main.js

Prüfungen ausführen (learnrun.js muss im selben Ordner liegen):

node --test
learnrun.js herunterladen

Übung 2 von 2

Klare Meldungen für eine Einstellungsdatei

loadSettings(name) liest die JSON-Datei name aus dem Ordner von main.js und gibt den ausgewerteten Wert zurück. Lassen Sie jedes erwartete Problem mit einem Error ablehnen, dessen message eine klare Zeile ist: "missing.json: no such file" bei ENOENT, ".: is a folder, not a file" bei EISDIR (hier war der Name "."), und "broken.json: not valid JSON", wenn JSON.parse scheitert. Jeden anderen Fehler werfen Sie unverändert weiter. Starten Sie node main.js und prüfen Sie mit node --test.

Diese Übung braucht Node.js auf Ihrem Computer (die Browser-Version kann sie nicht ausführen). Dateien und Befehle stehen unten.

Hinweise
  1. Hinweis 1

    Setzen Sie nur den readFile-Aufruf in ein eigenes try und JSON.parse in ein zweites, dann wissen Sie, welcher Schritt scheiterte.

  2. Hinweis 2

    Vergleichen Sie im ersten catch error.code mit 'ENOENT' und 'EISDIR', und enden Sie für den Rest mit throw error;.

  3. Hinweis 3

    throw new Error(`${name}: no such file`) lehnt die async-Funktion mit dieser Meldung ab.

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

import {readFile} from 'node:fs/promises';
import {join} from 'node:path';

// Reads a JSON file next to main.js and returns the parsed value.
export async function loadSettings(name) {
  let text;
  try {
    text = await readFile(join(import.meta.dirname, name), 'utf8');
  } catch (error) {
    if (error.code === 'ENOENT') throw new Error(`${name}: no such file`);
    if (error.code === 'EISDIR') throw new Error(`${name}: is a folder, not a file`);
    throw error;
  }
  try {
    return JSON.parse(text);
  } catch {
    throw new Error(`${name}: not valid JSON`);
  }
}

try {
  console.log(await loadSettings('settings.json'));
} catch (error) {
  console.error(error.message);
  process.exitCode = 1;
}
Auf dem eigenen Computer ausführen

Installieren Sie Node.js 24 LTS oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.js

import {readFile} from 'node:fs/promises';
import {join} from 'node:path';

// Reads a JSON file next to main.js and returns the parsed value.
export async function loadSettings(name) {
  const text = await readFile(join(import.meta.dirname, name), 'utf8');
  return JSON.parse(text);
}

try {
  console.log(await loadSettings('settings.json'));
} catch (error) {
  console.error(error.message);
  process.exitCode = 1;
}

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {loadSettings} from './main.js';

test('liest settings.json und wertet sie aus', async () => {
  const got = await loadSettings('settings.json');
  assert.deepEqual(got, {theme: 'dark', fontSize: 14}, `loadSettings lieferte ${JSON.stringify(got)}`);
});

test('eine fehlende Datei ergibt eine klare Meldung', async () => {
  await assert.rejects(loadSettings('missing.json'), {message: 'missing.json: no such file'}, 'erwartet war die Meldung "missing.json: no such file"');
});

test('ein Ordner ergibt eine klare Meldung', async () => {
  await assert.rejects(loadSettings('.'), {message: '.: is a folder, not a file'}, 'erwartet war die Meldung ".: is a folder, not a file"');
});

test('fehlerhaftes JSON ergibt eine klare Meldung', async () => {
  await assert.rejects(loadSettings('broken.json'), {message: 'broken.json: not valid JSON'}, 'erwartet war die Meldung "broken.json: not valid JSON"');
});

settings.json

{"theme": "dark", "fontSize": 14}

broken.json

{theme: "dark"}

package.json

{
  "type": "module"
}

package.json sagt Node.js, dass die .js-Dateien Module sind; lassen Sie die Datei im Ordner.

Programm ausführen:

node main.js

Prüfungen ausführen (learnrun.js muss im selben Ordner liegen):

node --test
learnrun.js herunterladen

Häufige Fehler

Das Promise statt des Texts auswerten

import {readFile} from 'node:fs/promises';

const settings = JSON.parse(readFile('settings.json', 'utf8'));
console.log(settings.theme);

Was Node.js ausgibt

SyntaxError: Unexpected token 'o', "[object Promise]" is not valid JSON

Warum, und die Lösung

readFile aus node:fs/promises gibt ein Promise zurück, nicht den Text. JSON.parse wandelt sein Argument zuerst in einen String um, das ergibt "[object Promise]", und das ist kein JSON. Warten Sie das Lesen ab: JSON.parse(await readFile("settings.json", "utf8")).

Einen Buffer wie einen String behandeln

import {readFile} from 'node:fs/promises';

const text = await readFile('names.txt');
console.log(text.split('\n'));

Was Node.js ausgibt

TypeError: text.split is not a function

Warum, und die Lösung

Ohne Kodierung liefert readFile einen Buffer aus Bytes, und ein Buffer hat keine Methode split. Verlangen Sie beim Lesen Text: await readFile("names.txt", "utf8"). Dann funktionieren split, trim und die anderen String-Methoden.

err im Callback ignorieren

import {readFile} from 'node:fs';

readFile('setings.json', 'utf8', (err, text) => {
  console.log(text.trim());
});

Was Node.js ausgibt

TypeError: Cannot read properties of undefined (reading 'trim')

Warum, und die Lösung

Die Callback-Form übergibt zuerst den Fehler. Hier ist der Name falsch geschrieben, also ist err ein ENOENT-Fehler und text undefined, und der Absturz verdeckt die eigentliche Ursache. Prüfen Sie err, bevor Sie die Daten nutzen: if (err) { console.error(err.code); return; }. Oder nehmen Sie die Promise-Form mit try/catch.

JavaScript im Browser: die eigene Engine Ihres Browsers in einem abgeschotteten Worker. Syntaxfehler findet acorn 8.18.0, MIT. Lizenz und Quellcode

Abschlussquiz

5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.

Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.

Problem melden

Etwas ist falsch oder unklar? Beschreiben Sie es kurz, dann wird es geprüft und korrigiert.

#

Mindestens 20 Zeichen.

Nur, wenn Sie eine Antwort wünschen.

Kernideen

Bytes oder Text: Die Kodierung entscheidet

readFile(path) aus node:fs/promises liest die ganze Datei in den Speicher. Ohne Kodierung erfüllt es sich mit einem Buffer, den rohen Bytes: console.log zeigt <Buffer 48 69 0a>, und ein Buffer hat kein split. Mit 'utf8' oder {encoding: 'utf8'} erhalten Sie einen String. JSON.parse(text) macht aus JSON-Text einen Wert; fehlerhaftes JSON wirft einen SyntaxError, der kein Dateifehler ist und keinen error.code hat. Für Zeilen schneidet text.split(/\r?\n/) an jedem Zeilenende, auch dem von Windows; ein abschließender Zeilenumbruch hinterlässt ein leeres letztes Element, also filtern Sie leere Zeilen heraus. Sehr große Dateien liest man besser mit Streams, später im Kurs.

error.code sagt, was schiefging

Ein fehlgeschlagenes Lesen wird mit einem Fehler abgelehnt, dessen code das Problem benennt. ENOENT: Unter dem Pfad gibt es weder Datei noch Ordner. EISDIR: Der Pfad ist ein Ordner, readFile will aber eine Datei. EACCES: Die Datei existiert, aber ihre Rechte verbieten Ihnen das Lesen. Die Doku nennt error.code den stabilsten Weg, einen Fehler zu erkennen; die Meldung kann sich zwischen Versionen ändern, also vergleichen Sie den Code, nie den Meldungstext. B1.1 fiel bei ENOENT auf einen Ersatzwert zurück. Behandeln Sie jeden erwarteten Code mit einer eigenen klaren Meldung und werfen Sie alles andere weiter, damit ein echter Fehler das Programm trotzdem stoppt.

Drei Formen, und wohin ein relativer Pfad zeigt

Jede fs-Operation gibt es in drei Formen. Die Promise-Form, readFile aus node:fs/promises, wird mit await abgewartet. Die Callback-Form, readFile aus node:fs, ruft am Ende (err, data) auf; bei Erfolg ist err null, prüfen Sie es also zuerst. Die Sync-Form, readFileSync, gibt die Daten zurück und blockiert: Bis die Datei gelesen ist, läuft kein anderes JavaScript, und Fehler werden geworfen. Ein relativer Pfad wie 'data.txt' wird gegen process.cwd() aufgelöst, den Ordner, in dem node gestartet wurde, nicht gegen den Ordner Ihres Moduls. Für eine Datei, die neben main.js liegt, bilden Sie den Pfad mit join(import.meta.dirname, 'data.txt').

Quellen

Zuletzt geprüft am 3. Oktober 2026