Menu

fs/promises в Node.js: работа с файлами через async/await

fs/promises это версия модуля fs в Node на промисах: readFile, writeFile, readdir, stat, mkdir, rm и rename возвращают промисы, которые вы ждёте через await, а ошибки ловятся через try/catch.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

fs/promises это версия модуля fs в Node на промисах. Его функции (readFile, writeFile, readdir, stat, mkdir, rm, rename и остальные) возвращают промис, а не принимают callback, поэтому вы ждёте их через await и ловите ошибки через try/catch. Импортируйте его через require('fs/promises') или import fs from 'fs/promises'.

Программа выводит [ 'write docs', 'ship it' ]: каждый await ждёт одну операцию, прежде чем выполнится следующая строка, поэтому дописывание всегда происходит после первой записи. В ES-модуле await работает на верхнем уровне; в CommonJS оберните код в функцию async.

Функции, которые нужны чаще всего

Имена совпадают с API на callback'ах, только без callback'а. Две из них подробно разобраны на страницах readFile и writeFile.

ФункцияЧто делаетЧем разрешается
readFile(path, 'utf8')Читает файл целикомСодержимым
writeFile(path, data)Создаёт или заменяет файлundefined
appendFile(path, data)Дописывает в конецundefined
readdir(path)Возвращает содержимое папкиМассивом имён
stat(path)Размер, даты, файл или папкаОбъектом Stats
mkdir(path, { recursive: true })Создаёт папкиПервой созданной папкой или undefined
rename(from, to)Перемещает или переименовываетundefined
rm(path, { recursive: true, force: true })Удаляет файл или папкуundefined

Содержимое папки через readdir и stat

readdir возвращает только имена. Вызовите stat для каждого, чтобы узнать размер или отличить файлы от папок, или передайте { withFileTypes: true }, чтобы получить записи, которые уже знают свой тип.

Цикл выводит README.md 6 bytes, notes.txt 3 bytes и src folder. Для несуществующего пути stat отклоняется с ENOENT, поэтому он годится как быстрая проверка существования, когда заодно нужны подробности.

Переименование, перемещение и удаление

rename и переименовывает, и перемещает файл (в пределах одного диска). rm удаляет; для непустой папки ему нужен recursive: true, а force: true, чтобы не падать, если пути уже нет.

Файл переезжает из drafts в published под новым именем, а после rm в папке blog остаётся только published. Уберите force: true из второго rm, и он отклонится с ENOENT.

Ошибки через try/catch

Неудачная операция отклоняет свой промис. С await это отклонение становится исключением, которое ловится как любое другое. Поле code у ошибки говорит, что пошло не так.

Первый вызов не находит файла и возвращает []; второй выводит Ada. Коды, которые стоит знать: ENOENT (нет такого файла), EEXIST (уже существует), EISDIR (папка там, где ожидался файл), ENOTEMPTY (в папке ещё что-то есть) и EACCES (нет прав).

Промис, который вы не ждёте через await и не обрабатываете через .catch(), при ошибке становится необработанным отклонением, и Node завершается с ошибкой. Ждите каждый вызов или возвращайте его промис вызывающему коду, который его дождётся.

Чтение нескольких файлов сразу через Promise.all

Цикл for с await читает файлы один за другим. Когда чтения не зависят друг от друга, запустите их все и подождите один раз через Promise.all: диск будет работать над ними одновременно.

Promise.all сохраняет порядок входного массива, поэтому результат будет [ 'alpha', 'beta', 'gamma' ], какое бы чтение ни закончилось первым, а длина склеенной строки равна 14. Замените в массиве 'b.txt' на 'x.txt', и весь await отклонится с ENOENT.

Если нужны все результаты, даже когда часть чтений не удалась, используйте Promise.allSettled; оба метода описаны на странице про промисы.

Частые ошибки

Путаница между двумя модулями. require('fs').readFile ждёт callback; require('fs/promises').readFile возвращает промис. Вызов версии на callback'ах без callback'а выбрасывает The "cb" argument must be of type function.

Забытый await. const text = fs.readFile(...) это промис в ожидании, и text.split выбрасывает text.split is not a function.

Параллельная запись в один файл. Promise.all с несколькими записями в один путь может оставить перемешанное содержимое. Выполняйте их последовательно.

Часто задаваемые вопросы

Чем fs.promises отличается от fs/promises?

Ничем. require('fs').promises и require('fs/promises') это один и тот же объект. Путь fs/promises (или node:fs/promises) сегодня обычный способ его импортировать.

Есть ли existsSync в fs/promises?

Нет. В API на промисах есть access, который отклоняется, если пути нет, но обычно просто вызывают нужную операцию и ловят ENOENT. existsSync из обычного модуля fs по-прежнему подходит для быстрых проверок.

fs/promises медленнее, чем API на callback'ах?

Чуть-чуть, из-за накладных расходов промисов, но рядом со временем работы диска это незаметно. Выбирайте по читаемости; большая часть нового кода использует API на промисах.

Как превратить функцию fs с callback'ом в промис?

Это редко нужно, потому что fs/promises покрывает функции для файлов. Для других API на callback'ах util.promisify(fn) возвращает версию, которая возвращает промис.

Иллюстрация языков программирования Coddy

Учитесь программировать с Coddy

Начать