1. Исторический экскурс
Когда-то в Node.js для работы с файлами был только модуль fs (File System), и все асинхронные методы работали через колбэки:
const fs = require('fs');
fs.readFile('data.txt', 'utf-8', (err, data) => {
if (err) {
console.error('Ошибка чтения файла:', err);
return;
}
console.log('Содержимое файла:', data);
});
Это работало, но с ростом сложности кода появлялись вложенные колбэки, от которых хотелось сбежать на край света.
С появлением промисов и синтаксиса async/await жизнь стала проще. Но чтобы использовать промисы с файловой системой, приходилось либо вручную оборачивать методы в промисы, либо использовать сторонние библиотеки.
Начиная с Node.js 10, появился модуль fs.promises, который содержит все основные методы для работы с файлами, но возвращает промисы, а не требует колбэков.
Почему это удобно?
- Код становится плоским и читаемым (никаких "лесенок" из колбэков).
- Можно использовать async/await, а значит — писать асинхронный код почти как синхронный.
- Современный подход, который рекомендуется использовать во всех новых проектах.
Как подключить и использовать fs.promises
В Node.js модуль fs.promises — это специальное свойство у стандартного fs. Для импорта используйте:
const fs = require('fs').promises;
Или, если вы используете ES-модули:
import { promises as fs } from 'fs';
После этого все методы, которые вы будете использовать (readFile, writeFile, unlink, и др.), возвращают промис.
2. Основные методы fs.promises и их применение
Чтение файла: fs.readFile
const fs = require('fs').promises;
async function readTextFile() {
try {
const data = await fs.readFile('data.txt', 'utf-8');
console.log('Содержимое файла:', data);
} catch (err) {
console.error('Ошибка чтения файла:', err);
}
}
readTextFile();
- Первый аргумент — путь к файлу.
- Второй аргумент — кодировка (например, 'utf-8'). Если не указать, получите буфер (не строку!).
Запись файла: fs.writeFile
async function writeTextFile() {
try {
await fs.writeFile('data.txt', 'Привет, Node.js!');
console.log('Файл успешно записан!');
} catch (err) {
console.error('Ошибка записи файла:', err);
}
}
writeTextFile();
- Если файла не существует — будет создан.
- Если существует — будет перезаписан (осторожно!).
Добавление в файл: fs.appendFile
async function appendTextFile() {
try {
await fs.appendFile('data.txt', '\nДобавленная строка!');
console.log('Данные успешно добавлены!');
} catch (err) {
console.error('Ошибка добавления данных:', err);
}
}
appendTextFile();
Удаление файла: fs.unlink
async function deleteFile() {
try {
await fs.unlink('data.txt');
console.log('Файл удалён!');
} catch (err) {
console.error('Ошибка удаления файла:', err);
}
}
deleteFile();
Проверка существования файла
Внимание: в fs.promises нет метода exists(). Вместо этого используйте обработку ошибок:
async function checkFileExists(path) {
try {
await fs.access(path);
console.log('Файл существует!');
} catch {
console.log('Файл не найден!');
}
}
checkFileExists('data.txt');
fs.access() выбрасывает ошибку, если файла нет.
4. Практика: мини-приложение "Заметки"
Давайте продолжим развивать наше мини-приложение — простую систему заметок, которую мы начинали в предыдущих лекциях.
Задача: список заметок хранится в файле notes.json. Нужно реализовать функции:
- Добавить новую заметку
- Прочитать все заметки
- Удалить заметку по индексу
1. Чтение всех заметок
const fs = require('fs').promises;
async function getNotes() {
try {
const data = await fs.readFile('notes.json', 'utf-8');
return JSON.parse(data);
} catch (err) {
// Если файла нет — возвращаем пустой массив
if (err.code === 'ENOENT') {
return [];
}
throw err;
}
}
(async () => {
const notes = await getNotes();
console.log('Все заметки:', notes);
})();
2. Добавление новой заметки
async function addNote(text) {
const notes = await getNotes();
notes.push({ text, created: new Date().toISOString() });
await fs.writeFile('notes.json', JSON.stringify(notes, null, 2));
console.log('Заметка добавлена!');
}
addNote('Купить хлеб');
3. Удаление заметки по индексу
async function removeNote(index) {
const notes = await getNotes();
if (index < 0 || index >= notes.length) {
console.log('Нет заметки с таким индексом');
return;
}
notes.splice(index, 1);
await fs.writeFile('notes.json', JSON.stringify(notes, null, 2));
console.log('Заметка удалена!');
}
removeNote(0);
4. Асинхронность и параллелизм
Важно помнить: если вы одновременно запускаете несколько операций записи в один и тот же файл, возможны гонки данных. Лучше избегать параллельных записей или использовать блокировки (но это тема для продвинутых курсов).
5. Типичные ошибки при работе с fs.promises и async/await
Ошибка №1: забыли добавить await перед асинхронным вызовом.
Если забыть await, функция вернёт промис, а не результат. В итоге, вместо данных вы получите [object Promise] или вообще ничего не произойдёт.
const data = fs.readFile('file.txt', 'utf-8'); // ошибка: data — это промис!
console.log(data); // Promise { <pending> }
Ошибка №2: используете await вне async-функции.
В CommonJS-модулях так делать нельзя, получите ошибку: "SyntaxError: await is only valid in async functions".
Ошибка №3: не обрабатываете ошибки.
Если не обернуть асинхронный код в try/catch, ошибки могут остаться незамеченными и привести к падению программы.
Ошибка №4: забыли указать кодировку при чтении текстовых файлов.
Если не указать 'utf-8', получите буфер, а не строку.
const data = await fs.readFile('file.txt'); // data — это Buffer!
Ошибка №5: параллельные записи в один и тот же файл.
Если одновременно запустить несколько функций, которые пишут в один и тот же файл, возможна потеря данных.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ