fs.readFile(path, 'utf8', callback) читает файл целиком и передаёт его содержимое в callback строкой. fs.readFileSync(path, 'utf8') возвращает строку сразу и ждёт, пока чтение закончится, а await readFile(path, 'utf8') из fs/promises делает то же самое без блокировки. Если не передать 'utf8', вместо текста вы получите Buffer с сырыми байтами.
Первым выводится readFile called: readFile только начинает чтение, а callback выполняется, когда содержимое готово. Удалите 'utf8', и запустите снова: callback получит <Buffer 42 75 79 20 6d 69 6c 6b 0a 43 61 6c 6c 20 41 64 61>, сырые байты файла.
readFile, readFileSync и fs/promises
Все три читают один и тот же файл. Отличаются они тем, как вы получаете результат и ждёт ли остальная программа.
Строка от синхронного чтения выводится первой, потому что пока оно не вернётся, больше ничего не выполняется. Два других заканчиваются после основного кода, в том порядке, в котором завершатся их чтения.
readFileSync | readFile (callback) | fs/promises readFile | |
|---|---|---|---|
| Возвращает | Содержимое | undefined | Промис с содержимым |
| Блокирует программу | Да | Нет | Нет |
| Ошибки | Выбрасываются, используйте try/catch | Первый аргумент callback'а | Отклонённый промис, try/catch с await |
| Где использовать | Код запуска и скрипты | Старый код на callback'ах | Новый код |
Используйте readFileSync, когда читаете конфиг при запуске или в коротком скрипте. На сервере синхронное чтение останавливает все остальные запросы, пока работает диск, поэтому там нужна версия на промисах. Остальной API описан на странице про fs/promises.
Если файла нет (ENOENT)
Когда файла не существует, у ошибки code: 'ENOENT' ("error, no entry", то есть «нет такой записи»). Проверяйте err.code, чтобы отличить отсутствующий файл от других сбоев, например от проблемы с правами.
Первый вызов не находит файла и возвращает запасное default settings; второй читает dark mode. Если пробрасывать остальные ошибки дальше, настоящие проблемы остаются видны, а не тихо подменяются значениями по умолчанию.
fs.existsSync: почему лучше просто прочитать
fs.existsSync(path) возвращает true или false, и хочется проверить файл перед чтением:
if (fs.existsSync('data.txt')) {
const text = fs.readFileSync('data.txt', 'utf8');
}
Это два шага там, где хватает одного, и файл могут удалить между ними, так что чтение всё равно может выбросить ошибку. Просто читайте и обрабатывайте ENOENT, как выше. existsSync подходит, когда нужно только узнать, существует ли что-то, и открывать это вы не будете, например чтобы решить, создавать ли папку.
Чтение JSON-файла
Прочитайте текст, а затем разберите его через JSON.parse. Файл с синтаксической ошибкой выбрасывает SyntaxError, поэтому держите его в том же try.
Программа выводит 3001 и boolean: после разбора значения получают свои типы из JSON. require('./config.json') тоже работает, но кэширует результат, поэтому повторный require вернёт старый объект, даже если файл изменился.
Чтение файла построчно
Файл, который помещается в память, прочитайте целиком и разбейте по символу новой строки. Отфильтруйте пустую строку, которая остаётся после последнего перевода строки.
Разбиение даёт 4 элемента, последний пустой, поэтому цикл выводит три пронумерованные строки. В файлах, записанных в Windows, строки заканчиваются на \r\n; разбивайте по /\r?\n/, чтобы обработать оба варианта.
Лог в несколько гигабайт в память не поместится. Читайте его потоком, чтобы в памяти была только одна строка за раз:
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();
Частые ошибки
Результат используют вне callback'а. const text = fs.readFile(...) равен undefined. Содержимое есть только внутри callback'а или после await.
Забыли кодировку. Без 'utf8' вы получаете Buffer, и data.split('\n') выбрасывает data.split is not a function. Передавайте кодировку всегда, когда нужен текст.
Пути относительно не той папки. 'data.txt' ищется там, откуда запущен node. Для файла, который лежит рядом со скриптом, используйте path.join(__dirname, 'data.txt').
Часто задаваемые вопросы
Почему fs.readFile возвращает undefined?
fs.readFile не возвращает содержимое. Он начинает чтение и позже вызывает ваш callback с (err, data). Используйте данные внутри callback'а, вызовите readFileSync или дождитесь версии на промисах через await.
Где Node ищет относительный путь вроде 'data.txt'?
В текущей рабочей папке, той, из которой вы запустили node, а не в папке скрипта. Чтобы прочитать файл рядом со скриптом, соберите путь через path.join(__dirname, 'data.txt').
Может ли fs.readFile прочитать файл по URL?
Нет, он читает только из файловой системы. Чтобы получить файл по HTTP, используйте fetch и затем await res.text().
Файл какого размера может прочитать readFile?
Он загружает весь файл в память, поэтому подходит для конфигов, шаблонов и небольших файлов с данными. Файлы в сотни мегабайт читайте по частям через fs.createReadStream.