Menu

fs/promises w Node.js: system plików z async/await

fs/promises to wersja modułu fs w Node oparta na promise: readFile, writeFile, readdir, stat, mkdir, rm i rename zwracają promise, na które czekasz przez await, a błędy łapiesz przez try/catch.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

fs/promises to wersja modułu fs w Node oparta na promise. Jego funkcje (readFile, writeFile, readdir, stat, mkdir, rm, rename i pozostałe) zwracają promise zamiast przyjmować callback, więc czekasz na nie przez await, a błędy łapiesz przez try/catch. Zaimportuj go przez require('fs/promises') albo import fs from 'fs/promises'.

Wypisuje [ 'write docs', 'ship it' ]: każde await czeka na jedną operację, zanim wykona się następna linia, więc dopisanie zawsze następuje po pierwszym zapisie. W module ES await działa na najwyższym poziomie; w CommonJS opakuj kod w funkcję async.

Funkcje, których będziesz używać najczęściej

Nazwy są takie jak w API z callbackami, tylko bez callbacku. Strony o readFile i writeFile opisują te dwie dokładnie.

FunkcjaCo robiRozwiązuje się do
readFile(path, 'utf8')Czyta cały plikZawartości
writeFile(path, data)Tworzy albo zastępuje plikundefined
appendFile(path, data)Dopisuje na końcuundefined
readdir(path)Listuje folderTablicy nazw
stat(path)Rozmiar, daty, plik czy folderObiektu Stats
mkdir(path, { recursive: true })Tworzy folderyPierwszego utworzonego folderu albo undefined
rename(from, to)Przenosi albo zmienia nazwęundefined
rm(path, { recursive: true, force: true })Usuwa plik albo folderundefined

Listowanie folderu przez readdir i stat

readdir zwraca tylko nazwy. Wywołaj stat dla każdej, żeby poznać rozmiar albo odróżnić pliki od folderów, albo przekaż { withFileTypes: true }, żeby dostać wpisy, które już znają swój typ.

Pętla wypisuje README.md 6 bytes, notes.txt 3 bytes i src folder. stat odrzuca z ENOENT dla ścieżki, która nie istnieje, więc to szybkie sprawdzenie istnienia, gdy chcesz też poznać szczegóły.

Zmiana nazwy, przenoszenie i usuwanie

rename zarówno zmienia nazwę, jak i przenosi plik (w obrębie tego samego dysku). rm usuwa; dla folderu z zawartością potrzebuje recursive: true, a force: true, żeby nie zawieść, gdy ścieżki już nie ma.

Plik przenosi się z drafts do published pod nową nazwą, a po rm folder blog zawiera tylko published. Usuń force: true z drugiego rm, a odrzuci z ENOENT.

Obsługa błędów przez try/catch

Nieudana operacja odrzuca swój promise. Z await to odrzucenie staje się wyjątkiem, który łapiesz jak każdy inny. code błędu mówi, co poszło nie tak.

Pierwsze wywołanie nie znajduje pliku i zwraca []; drugie wypisuje Ada. Kody, które warto znać: ENOENT (nie ma takiego pliku), EEXIST (już istnieje), EISDIR (folder tam, gdzie oczekiwano pliku), ENOTEMPTY (folder nadal ma zawartość) i EACCES (brak uprawnień).

Promise, na który nie czekasz przez await i nie dodasz .catch(), przy błędzie staje się nieobsłużonym odrzuceniem, a Node kończy działanie z błędem. Czekaj na każde wywołanie albo zwracaj jego promise do kodu, który to robi.

Czytanie kilku plików naraz przez Promise.all

Pętla for z await czyta pliki jeden po drugim. Gdy odczyty od siebie nie zależą, uruchom wszystkie i poczekaj raz przez Promise.all: dysk pracuje nad nimi równocześnie.

Promise.all zachowuje kolejność tablicy wejściowej, więc wynik to [ 'alpha', 'beta', 'gamma' ] niezależnie od tego, który odczyt skończył się pierwszy, a łączna długość to 14. Zmień w tablicy 'b.txt' na 'x.txt', a całe await odrzuci z ENOENT.

Jeśli potrzebujesz każdego wyniku, nawet gdy niektóre odczyty się nie udadzą, użyj Promise.allSettled; strona o promise opisuje oba.

Częste błędy

Mylenie dwóch modułów. require('fs').readFile oczekuje callbacku; require('fs/promises').readFile zwraca promise. Wywołanie wersji z callbackiem bez callbacku rzuca The "cb" argument must be of type function.

Zapominanie o await. const text = fs.readFile(...) to oczekujący promise, a text.split rzuca text.split is not a function.

Równoległe zapisy do jednego pliku. Promise.all z kilkoma zapisami do tej samej ścieżki może zostawić wymieszaną zawartość. Wykonuj je po kolei.

Najczęściej zadawane pytania

Czym różni się fs.promises od fs/promises?

Niczym. require('fs').promises i require('fs/promises') to ten sam obiekt. Ścieżka fs/promises (albo node:fs/promises) to dziś typowy sposób importu.

Czy w fs/promises jest existsSync?

Nie. API z promise ma access, które odrzuca, gdy ścieżki nie ma, ale typowy wzorzec to wywołać potrzebną operację i złapać ENOENT. existsSync ze zwykłego modułu fs nadal działa do szybkich sprawdzeń.

Czy fs/promises jest wolniejsze od API z callbackami?

Trochę, przez narzut promise, ale przy czasie dostępu do dysku to pomijalne. Wybieraj według czytelności; API z promise to to, którego używa większość nowego kodu.

Jak zamienić funkcję fs z callbackiem na promise?

Rzadko jest to potrzebne, bo fs/promises obejmuje funkcje plikowe. Dla innych API z callbackami util.promisify(fn) zwraca wersję, która zwraca promise.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

Zacznij