Введение в работу с файловой системой в TypeScript
TypeScript — это строго типизированное расширение JavaScript, которое добавляет статическую типизацию и улучшает читаемость и поддерживаемость кода. Одной из ключевых задач при разработке серверных приложений является работа с файловой системой: чтение и запись файлов, создание и удаление директорий, управление потоками данных. В этой статье мы подробно разберём, как эффективно работать с файловой системой в TypeScript, используя встроенный модуль fs из Node.js.
Модуль fs предоставляет как синхронные, так и асинхронные методы для взаимодействия с файловой системой. В TypeScript мы можем воспользоваться преимуществами типизации, чтобы сделать код более надёжным и понятным. Мы рассмотрим основные операции, включая чтение, запись, проверку существования файлов, работу с директориями и обработку ошибок.
Основные операции с файлами
Чтение файлов
Для чтения содержимого файла в TypeScript можно использовать метод readFile (асинхронный) или readFileSync (синхронный). Асинхронный подход рекомендуется для неблокирующего выполнения программы.
import { readFile } from 'fs/promises';
async function readFileExample(filePath: string): Promise<string> {
try {
const data = await readFile(filePath, 'utf-8');
return data;
} catch (error) {
console.error('Ошибка при чтении файла:', error);
throw error;
}
}
// Использование
readFileExample('example.txt').then(content => {
console.log('Содержимое файла:', content);
});
Обратите внимание на использование fs/promises — это промис-обёртка, которая позволяет использовать синтаксис async/await. Тип возвращаемого значения Promise<string> явно указывает, что функция возвращает строку.
Запись в файлы
Запись данных в файл выполняется методами writeFile и writeFileSync. В TypeScript важно корректно обрабатывать возможные ошибки, особенно если файл защищён от записи или путь не существует.
import { writeFile, mkdir } from 'fs/promises';
import { existsSync } from 'fs';
async function writeFileExample(filePath: string, data: string): Promise<void> {
try {
// Проверяем, существует ли директория
const dir = filePath.substring(0, filePath.lastIndexOf('/'));
if (dir && !existsSync(dir)) {
await mkdir(dir, { recursive: true });
}
await writeFile(filePath, data, 'utf-8');
console.log('Файл успешно записан');
} catch (error) {
console.error('Ошибка при записи файла:', error);
}
}
// Использование
writeFileExample('data/output.txt', 'Hello, TypeScript!');
В этом примере мы сначала проверяем существование директории и создаём её при необходимости с помощью mkdir с опцией recursive: true. Это позволяет избежать ошибки "ENOENT", если родительская папка не существует.
Добавление данных в конец файла
Если нужно дописать данные в существующий файл, используйте метод appendFile:
import { appendFile } from 'fs/promises';
async function appendToFile(filePath: string, data: string): Promise<void> {
try {
await appendFile(filePath, '\n' + data, 'utf-8');
console.log('Данные добавлены');
} catch (error) {
console.error('Ошибка при добавлении:', error);
}
}
Работа с директориями
Создание и удаление директорий
Для создания директорий используется mkdir, для удаления — rmdir или rm (для рекурсивного удаления). В TypeScript удобно использовать дженерики для типизации путей.
import { mkdir, rmdir, rm } from 'fs/promises';
import { existsSync } from 'fs';
async function createDirectory(dirPath: string): Promise<void> {
if (!existsSync(dirPath)) {
await mkdir(dirPath, { recursive: true });
console.log('Директория создана');
} else {
console.log('Директория уже существует');
}
}
async function removeDirectory(dirPath: string, recursive: boolean = false): Promise<void> {
try {
if (recursive) {
await rm(dirPath, { recursive: true, force: true });
} else {
await rmdir(dirPath);
}
console.log('Директория удалена');
} catch (error) {
console.error('Ошибка при удалении:', error);
}
}
Чтение содержимого директории
Метод readdir возвращает массив имён файлов и папок. Для фильтрации только файлов или только папок можно использовать stat.
import { readdir, stat } from 'fs/promises';
interface FileInfo {
name: string;
isDirectory: boolean;
size: number;
}
async function listDirectory(dirPath: string): Promise<FileInfo[]> {
try {
const files = await readdir(dirPath);
const fileInfos: FileInfo[] = [];
for (const file of files) {
const fullPath = `${dirPath}/${file}`;
const stats = await stat(fullPath);
fileInfos.push({
name: file,
isDirectory: stats.isDirectory(),
size: stats.size
});
}
return fileInfos;
} catch (error) {
console.error('Ошибка при чтении директории:', error);
return [];
}
}
// Использование
listDirectory('./').then(files => {
files.forEach(f => {
console.log(`${f.name} - ${f.isDirectory ? 'папка' : 'файл'} (${f.size} байт)`);
});
});
Обратите внимание на интерфейс FileInfo — это пример типизации, которая делает код самодокументируемым и безопасным.
Обработка ошибок и типизация
Типы ошибок Node.js
При работе с файловой системой часто возникают ошибки: файл не найден (ENOENT), недостаточно прав (EACCES), диск переполнен (ENOSPC). В TypeScript можно создать кастомные типы для обработки ошибок.
import { readFile } from 'fs/promises';
type FileError = {
code: string;
message: string;
};
async function safeReadFile(filePath: string): Promise<string | FileError> {
try {
const data = await readFile(filePath, 'utf-8');
return data;
} catch (error: any) {
return {
code: error.code || 'UNKNOWN',
message: error.message || 'Неизвестная ошибка'
};
}
}
// Использование с проверкой типа
const result = await safeRead