1. Введение
Если вы когда-нибудь пытались прочитать или записать файл с помощью обычного JavaScript в браузере, то наверняка уже знаете: у браузера на это жёсткое табу. А вот Node.js, как настоящий серверный самурай, открывает для нас двери в мир файловой системы.
Модуль fs (от англ. file system) — это стандартный модуль Node.js, который позволяет работать с файлами и папками: читать, писать, удалять, создавать директории, копировать, перемещать и многое другое.
Факт: Если бы Node.js не умел работать с файлами, его бы никто не использовал для серверной разработки — ведь хранить данные где-то нужно!
Импортируем модуль fs
Начнём с самого простого. Чтобы использовать модуль fs, его нужно импортировать. В Node.js (CommonJS) это делается так:
const fs = require('fs');
Если вы используете ES Modules (например, с расширением .mjs или с "type": "module" в package.json):
import fs from 'fs';
В этой лекции мы будем использовать синтаксис CommonJS, так как он наиболее распространён для работы с fs.
2. Синхронные и асинхронные методы: в чём разница?
Модуль fs предоставляет две версии почти каждой функции:
- Асинхронная (рекомендуемая) — обычно принимает колбэк или возвращает Promise (в промисовой версии, о ней поговорим позже).
- Синхронная — выполняет операцию и блокирует поток до завершения.
Пример:
Асинхронно: fs.readFile(path, callback)
Синхронно: fs.readFileSync(path)
Почему это важно?
Node.js работает по однопоточному принципу: пока одна операция выполняется синхронно, остальные пользователи могут ждать в очереди. Поэтому для серверов и большинства приложений всегда используйте асинхронные методы! Синхронные — только для скриптов, которые запускаются разово (например, миграции или генерация файлов).
Ниже в этой лекции мы только познакомимся с различными методами модуля fs. А в следующих лекциях разберем их подробно.
3. Чтение файлов: асинхронно и синхронно
Асинхронное чтение файла
Давайте попробуем прочитать файл hello.txt (создайте его заранее и напишите туда что-нибудь приятное):
const fs = require('fs');
fs.readFile('hello.txt', 'utf8', (err, data) => {
if (err) {
console.error('Ошибка при чтении файла:', err);
return;
}
console.log('Содержимое файла:', data);
});
Разбор кода:
- 'hello.txt' — путь к файлу (можно относительный или абсолютный).
- 'utf8' — кодировка (иначе получите Buffer, а не строку).
- Колбэк принимает два аргумента: err (ошибка, если что-то пошло не так) и data (содержимое файла).
Аналогия: Асинхронное чтение — как если бы вы заказали пиццу и продолжили смотреть сериал, а курьер позвонит, когда привезёт.
Синхронное чтение файла
const fs = require('fs');
try {
const data = fs.readFileSync('hello.txt', 'utf8');
console.log('Содержимое файла:', data);
} catch (err) {
console.error('Ошибка при чтении файла:', err);
}
Здесь выполнение программы останавливается на момент чтения файла, пока операция не завершится. Если файла нет — будет выброшено исключение, поэтому используем try...catch.
4. Запись файлов: асинхронно и синхронно
Асинхронная запись файла
const fs = require('fs');
fs.writeFile('output.txt', 'Привет, Node.js!', 'utf8', (err) => {
if (err) {
console.error('Ошибка при записи файла:', err);
return;
}
console.log('Файл успешно записан!');
});
- Если файла не было — он будет создан.
- Если файл был — его содержимое перезапишется.
Синхронная запись файла
const fs = require('fs');
try {
fs.writeFileSync('output.txt', 'Привет, Node.js!', 'utf8');
console.log('Файл успешно записан!');
} catch (err) {
console.error('Ошибка при записи файла:', err);
}
5. Добавление данных в файл: appendFile и appendFileSync
Иногда нужно не перезаписывать файл, а добавить к нему новые данные — например, для логов.
Асинхронно
const fs = require('fs');
fs.appendFile('log.txt', 'Новая запись\n', 'utf8', (err) => {
if (err) {
console.error('Ошибка при добавлении в файл:', err);
return;
}
console.log('Запись добавлена в log.txt');
});
Синхронно
const fs = require('fs');
try {
fs.appendFileSync('log.txt', 'Новая запись\n', 'utf8');
console.log('Запись добавлена в log.txt');
} catch (err) {
console.error('Ошибка при добавлении в файл:', err);
}
6. Проверка существования файла: fs.existsSync и fs.access
Синхронная проверка
const fs = require('fs');
if (fs.existsSync('hello.txt')) {
console.log('Файл существует!');
} else {
console.log('Файл не найден!');
}
Асинхронная проверка (современный способ)
Метод fs.exists считается устаревшим. Используйте fs.access:
const fs = require('fs');
fs.access('hello.txt', fs.constants.F_OK, (err) => {
if (err) {
console.log('Файл не найден!');
} else {
console.log('Файл существует!');
}
});
7. Удаление файлов: unlink и unlinkSync
Асинхронно
const fs = require('fs');
fs.unlink('output.txt', (err) => {
if (err) {
console.error('Ошибка при удалении файла:', err);
return;
}
console.log('Файл удалён!');
});
Синхронно
const fs = require('fs');
try {
fs.unlinkSync('output.txt');
console.log('Файл удалён!');
} catch (err) {
console.error('Ошибка при удалении файла:', err);
}
8. Пример: мини-логгер на Node.js
Давайте напишем простую функцию для логирования сообщений в файл. Это будет маленькая, но полезная часть нашего будущего приложения.
const fs = require('fs');
/**
* Добавляет строку в лог-файл с текущей датой и временем.
* @param {string} message Сообщение для лога
*/
function logMessage(message) {
const now = new Date().toISOString();
const logLine = `[${now}] ${message}\n`;
fs.appendFile('app.log', logLine, 'utf8', (err) => {
if (err) {
console.error('Ошибка при логировании:', err);
}
});
}
// Пример использования:
logMessage('Приложение запущено');
logMessage('Что-то произошло...');
Совет: Не используйте синхронные методы для логирования в настоящих приложениях — иначе сервер может "зависнуть" на записи!
9. Особенности путей: относительный и абсолютный
Если указать только имя файла (например, 'hello.txt'), путь считается относительным к рабочей директории (обычно это папка, из которой вы запустили Node.js).
Чтобы избежать путаницы, используйте модуль path и переменную __dirname:
const fs = require('fs');
const path = require('path');
const filePath = path.join(__dirname, 'hello.txt');
fs.readFile(filePath, 'utf8', (err, data) => {
// ...
});
10. Асинхронность: почему это важно для серверов
Когда вы используете асинхронные методы, Node.js не блокирует выполнение программы. Это особенно важно для серверов, которые должны быстро отвечать многим пользователям одновременно.
Пример:
Если вы используете fs.readFileSync в обработчике HTTP-запроса, то пока файл читается, сервер не сможет обрабатывать другие запросы. Если пользователей много — все будут ждать, пока закончится операция.
11. Типичные ошибки при работе с модулем fs
Ошибка №1: Использование синхронных методов в серверном коде.
Синхронные методы блокируют весь поток Node.js, что приводит к "тормозам" при большом количестве запросов. Используйте их только для мелких скриптов или в инициализации.
Ошибка №2: Необработка ошибок в колбэках.
Если забыть проверить аргумент err в асинхронных методах, можно не заметить, что файл не был прочитан или записан. Всегда обрабатывайте ошибку — хотя бы выводите её в консоль.
Ошибка №3: Ожидание, что файл всегда будет существовать.
Не проверяя существование файла перед чтением, вы рискуете получить ошибку и "упавшее" приложение. Лучше использовать fs.access или ловить ошибку в колбэке/try-catch.
Ошибка №4: Неправильная работа с путями.
Использование относительных путей без понимания, где находится рабочая директория, часто приводит к "файл не найден". Используйте path.join(__dirname, ...) для надёжности.
Ошибка №5: Одновременная запись в один и тот же файл.
Если несколько асинхронных операций пишут в один файл одновременно, можно получить "кашу" или потерю данных. Для логов используйте очереди или специальные библиотеки, если нагрузка большая.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ