Menu

Чтение файла в Node.js: fs.readFile, readFileSync, промисы

Файл в Node.js читают через fs.readFile (callback), fs.readFileSync или await readFile из fs/promises. Передайте 'utf8', чтобы получить строку, и обрабатывайте ENOENT, когда файла нет.

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

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

Все три читают один и тот же файл. Отличаются они тем, как вы получаете результат и ждёт ли остальная программа.

Строка от синхронного чтения выводится первой, потому что пока оно не вернётся, больше ничего не выполняется. Два других заканчиваются после основного кода, в том порядке, в котором завершатся их чтения.

readFileSyncreadFile (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.

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

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

Начать