Abschnitt 6 · Lektion 5
Blockierender Code
Jede Funktion aus node:fs hat einen Zwilling mit Sync am Ende. readFile und readFileSync,
writeFile und writeFileSync, readdir und readdirSync. Die synchrone Variante sieht netter
aus: kein await, kein Promise, das Ergebnis steht direkt da.
Und genau deshalb steht sie in so vielen Projekten an der falschen Stelle.
Was der Zwilling wirklich tut
import { writeFileSync, readFileSync } from "node:fs";
writeFileSync("daten.txt", "x".repeat(200000));
setTimeout(() => console.log("Timer, angemeldet vor dem Lesen"), 0);
for (let i = 0; i < 20; i += 1) {
readFileSync("daten.txt", "utf8");
}
console.log("20 Mal synchron gelesen"); Der Timer war angemeldet, bevor die Schleife losging. Er kommt trotzdem erst danach dran, weil
zwischen den zwanzig Lesevorgängen kein einziger Moment war, in dem Node etwas anderes hätte tun
können. readFileSync gibt den Thread nicht ab. Es hält ihn fest, bis die Festplatte geantwortet
hat.
import { writeFile, readFile } from "node:fs/promises";
await writeFile("daten.txt", "x".repeat(200000));
setTimeout(() => console.log("Timer, angemeldet vor dem Lesen"), 0);
for (let i = 0; i < 20; i += 1) {
await readFile("daten.txt", "utf8");
}
console.log("20 Mal asynchron gelesen"); Dieselbe Arbeit, dieselbe Reihenfolge im Code, ein anderer Verlauf: Der Timer kommt zwischendurch
dran. Bei jedem await gibt Node den Thread frei und erledigt, was sonst noch ansteht.
Auf einem Server ist dieser Timer keine Kleinigkeit, sondern die Anfrage von jemand anderem.
Ein readFileSync in einer Route hält alle anderen Anfragen an, so lange es dauert. Und das
sieht man beim Entwickeln nie: Allein auf dem eigenen Rechner gibt es niemanden, den man aufhalten
könnte.
Wann es trotzdem richtig ist
Beim Start. Wenn dein Server hochfährt und die Konfiguration einliest, das Zertifikat lädt oder
eine Vorlagendatei holt, wartet noch niemand. Es gibt keine offenen Anfragen, die man blockieren
könnte, und der Code wird ohne await schlicht kürzer und geradliniger.
Dasselbe gilt für kleine Werkzeuge, die einmal laufen und wieder aufhören: ein Skript, das eine Datei umbaut und sich beendet, hat niemanden, dem es im Weg steht.
Die Frage lautet also nicht „synchron oder asynchron”, sondern „wartet gerade jemand?” Beim Hochfahren: nein. In einer Route, einem Handler, einem Zeitgeber: immer.
Der zweite Fall, mit dem niemand rechnet
Bis hierhin ging es um Aufrufe, die auf etwas warten. Es gibt einen zweiten Weg, den Thread festzuhalten, und der ist unauffälliger: eigener Code, der einfach lange rechnet.
Eine Sortierung über eine große Liste. Ein JSON.parse über zwanzig Megabyte. Eine Bildumrechnung.
Eine Schleife über ein paar Millionen Einträge. Das ist kein Warten, das ist Arbeit, und Arbeit
lässt sich nicht auf jemand anderen abwälzen.
const rechnen = async () => {
let summe = 0;
for (let i = 0; i < 50_000_000; i += 1) summe += i;
return summe;
};
setTimeout(() => console.log("Timer, angemeldet vor dem Rechnen"), 0);
await rechnen();
console.log("Rechnen fertig"); Die Funktion ist async, und trotzdem läuft der Timer erst danach. async allein macht nichts
nebenläufig. Der Rumpf einer async-Funktion läuft ganz normal weiter, bis das erste await kommt,
und wenn keines kommt, läuft er eben komplett durch.
await beschleunigt nichts, es gibt nur ab. Wo es nichts abzugeben gibt, ändert es nichts.
Die Wege aus einer Rechenlast
Drei gibt es, je nach Fall.
Aufteilen. Die Arbeit in Stücke zerlegen und zwischen den Stücken kurz abgeben, etwa mit
setImmediate. Danach ist die Gesamtdauer dieselbe, aber der Server bleibt zwischendurch
ansprechbar.
Einen Worker nehmen. node:worker_threads gibt dir einen echten zweiten Thread mit eigener
Ereignisschleife. Für dauerhaft rechenintensive Aufgaben ist das die saubere Lösung.
Woandershin geben. Die Aufgabe in eine Warteschlange legen und von einem separaten Prozess erledigen lassen. Der Nutzer bekommt sofort eine Antwort und das Ergebnis später.
Für diesen Kurs reicht die Erkenntnis, dass es diese Wege gibt. Wichtiger ist die Regel davor, und
die nimmst du mit in Abschnitt 8, wo der erste Server entsteht: In allem, was auf eine Anfrage hin
läuft, gibt es kein Sync.
Zum Mitnehmen
Ein readFileSync in einer Route hält alle anderen Anfragen an. Beim Start ist derselbe Aufruf völlig in Ordnung.
Jetzt du
Basis Konto, kostenlosZu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.
Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.
In diesem Kurs läuft dein Code auf einem Server. Dafür hat das Basis Konto 1 Stunde im Monat, mehr Zeit gibt es mit dem Premium Konto.
Was in dieser Lektion steckt
-
Artikel mit 3 Beispielen zum Ausprobieren
Steht hier, ohne Konto lesbar.
-
Aufgabe, dein Code läuft auf einem Server
Öffnet sich mit dem Basis Konto.