fs.readFile(path, 'utf8', callback) czyta cały plik i przekazuje jego zawartość do callbacku jako string. fs.readFileSync(path, 'utf8') zwraca string bezpośrednio i czeka na koniec odczytu, a await readFile(path, 'utf8') z fs/promises robi to samo bez blokowania. Jeśli pominiesz 'utf8', zamiast tekstu dostaniesz Buffer z surowymi bajtami.
Najpierw wypisuje się readFile called: readFile tylko rozpoczyna odczyt, a callback wykonuje się, gdy zawartość jest gotowa. Usuń 'utf8', i uruchom ponownie: callback dostaje <Buffer 42 75 79 20 6d 69 6c 6b 0a 43 61 6c 6c 20 41 64 61>, czyli surowe bajty pliku.
readFile vs readFileSync vs fs/promises
Wszystkie trzy czytają ten sam plik. Różnią się tym, jak dostajesz wynik i czy reszta programu czeka.
Linia synchroniczna wypisuje się pierwsza, bo nic innego się nie wykonuje, dopóki ona nie wróci. Pozostałe dwie kończą się po głównym kodzie, w kolejności, w jakiej zakończą się ich odczyty.
readFileSync | readFile (callback) | fs/promises readFile | |
|---|---|---|---|
| Zwraca | Zawartość | undefined | Promise z zawartością |
| Blokuje program | Tak | Nie | Nie |
| Błędy | Rzucane, użyj try/catch | Pierwszy argument callbacku | Odrzucony promise, try/catch z await |
| Gdzie używać | Kod startowy i skrypty | Starszy kod z callbackami | Nowy kod |
Używaj readFileSync przy odczycie konfiguracji na starcie albo w krótkim skrypcie. Na serwerze odczyt synchroniczny zatrzymuje każde inne żądanie, dopóki dysk pracuje, więc tam użyj wersji z promise. Strona o fs/promises opisuje resztę tego API.
Obsługa brakującego pliku (ENOENT)
Gdy plik nie istnieje, błąd ma code: 'ENOENT' ("error, no entry"). Sprawdzaj err.code, żeby odróżnić brak pliku od innych błędów, np. problemu z uprawnieniami.
Pierwsze wywołanie nie znajduje pliku i wraca do default settings; drugie odczytuje dark mode. Ponowne rzucanie pozostałych błędów sprawia, że prawdziwe problemy są widoczne, zamiast po cichu używać wartości domyślnych.
fs.existsSync: dlaczego lepiej po prostu czytać
fs.existsSync(path) zwraca true albo false i kusi, żeby sprawdzać przed odczytem:
if (fs.existsSync('data.txt')) {
const text = fs.readFileSync('data.txt', 'utf8');
}
To dwa kroki tam, gdzie wystarczy jeden, a plik może zostać usunięty między nimi, więc odczyt i tak może rzucić wyjątek. Po prostu czytaj i obsługuj ENOENT, jak wyżej. existsSync jest w porządku, gdy chcesz tylko wiedzieć, czy coś istnieje, i nie będziesz tego otwierać, np. żeby zdecydować, czy utworzyć folder.
Odczyt pliku JSON
Odczytaj tekst, a potem sparsuj go przez JSON.parse. Plik z błędem składni rzuca SyntaxError, więc trzymaj to w tym samym try.
Wypisuje 3001 i boolean: po sparsowaniu wartości mają swoje typy z JSON. require('./config.json') też działa, ale cache'uje wynik, więc drugie require zwraca stary obiekt, nawet gdy plik się zmienił.
Czytanie pliku linia po linii
Plik, który mieści się w pamięci, odczytaj w całości i podziel po znaku nowej linii. Odfiltruj pusty string, który zostaje po końcowym znaku nowej linii.
Podział daje 4 elementy, ostatni pusty, więc pętla wypisuje trzy ponumerowane linie. Pliki zapisane w Windows kończą linie przez \r\n; dziel po /\r?\n/, żeby obsłużyć oba przypadki.
Log o rozmiarze kilku gigabajtów nie zmieści się w pamięci. Zamiast tego czytaj go strumieniem, żeby w pamięci była tylko jedna linia naraz:
const fs = require('fs');
const readline = require('readline');
async function main() {
const rl = readline.createInterface({
input: fs.createReadStream('big.log'),
crlfDelay: Infinity,
});
for await (const line of rl) {
console.log(line);
}
}
main();
Częste błędy
Używanie wyniku poza callbackiem. const text = fs.readFile(...) to undefined. Zawartość istnieje tylko w callbacku albo po await.
Zapominanie o kodowaniu. Bez 'utf8' dostajesz Buffer, a data.split('\n') rzuca data.split is not a function. Przekazuj kodowanie zawsze, gdy chcesz tekst.
Ścieżki względem złego folderu. 'data.txt' jest szukany tam, gdzie uruchomiono node. Użyj path.join(__dirname, 'data.txt') dla pliku, który leży obok twojego skryptu.
Najczęściej zadawane pytania
Dlaczego fs.readFile zwraca undefined?
fs.readFile nie zwraca zawartości. Rozpoczyna odczyt i później wywołuje twój callback z (err, data). Używaj danych w callbacku, wywołaj readFileSync albo poczekaj przez await na wersję z promise.
Gdzie Node szuka ścieżki względnej, np. 'data.txt'?
W bieżącym katalogu roboczym, czyli folderze, z którego uruchomiłeś node, a nie w folderze skryptu. Żeby odczytać plik leżący obok skryptu, zbuduj ścieżkę przez path.join(__dirname, 'data.txt').
Czy fs.readFile może odczytać plik z URL?
Nie, czyta tylko z systemu plików. Żeby pobrać plik przez HTTP, użyj fetch, a potem await res.text().
Jak duży plik obsłuży readFile?
Wczytuje cały plik do pamięci, więc nadaje się do konfiguracji, szablonów i małych plików z danymi. Pliki o rozmiarze setek megabajtów czytaj kawałkami przez fs.createReadStream.