Покоряем файловую систему: работа с файлами в JavaScript

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: почему работа с файлами в JavaScript — это мастхэв

Представьте, что вы — капитан космического корабля. Ваша задача — управлять грузовыми отсеками, запасами кислорода и бортовым журналом. В мире веб-разработки файловая система — это те самые отсеки, а JavaScript — ваш пульт управления. Без умения работать с файлами вы застрянете на орбите: не сможете сохранить настройки пользователя, загрузить конфигурацию сервера или обработать лог-файлы.

Многие новички думают, что JavaScript живёт только в браузере и не умеет трогать файлы на диске. Это миф! С появлением Node.js (и даже в современных браузерах через File API) JavaScript стал полноценным инструментом для работы с файловой системой. Сегодня мы разберём, как читать, писать, удалять и перемещать файлы, а также как не попасть в ловушку асинхронности.

Кстати, если вы только начинаете свой путь в программировании, советую сначала изучить основы JavaScript — это поможет быстрее разобраться с примерами ниже.

Где JavaScript работает с файлами? Два мира — два подхода

Прежде чем нырять в код, давайте проясним: файловые операции в JS делятся на два лагеря.

1. Серверный JavaScript (Node.js)

Здесь у нас полный доступ к файловой системе: создание, удаление, переименование, чтение потоков — всё, что душе угодно. Работаем через встроенный модуль fs (File System). Это как швейцарский нож для работы с файлами.

2. Клиентский JavaScript (браузер)

Браузер — это «песочница» с ограничениями. Вы не можете просто так взять и прочитать любой файл с компьютера пользователя (иначе бы хакеры праздновали). Но можно работать с файлами, которые пользователь сам выбрал через <input type="file">, или через API FileReader и Blob.

В этой статье мы сфокусируемся на Node.js, но в конце дадим бонус — пример для браузера.

Модуль fs: ваш главный инструмент

В Node.js работа с файлами начинается с подключения модуля fs:

const fs = require('fs'); // старый стиль
// или
import fs from 'fs'; // если используете ES-модули

У fs есть три режима работы: синхронный, асинхронный на колбэках и асинхронный на промисах. Мы будем использовать последний — с async/await, так как это современный и читаемый подход.

Чтение файла: от простого к сложному

Допустим, у нас есть файл data.txt с текстом. Самый простой способ прочитать его:

import { readFile } from 'fs/promises';

async function readMyFile() {
  try {
    const data = await readFile('data.txt', 'utf-8');
    console.log(data);
  } catch (err) {
    console.error('Ошибка чтения:', err.message);
  }
}

readMyFile();

Что здесь важно? 'utf-8' — кодировка. Если её не указать, вы получите сырой буфер (Buffer), который нужно будет преобразовать в строку. Для текстовых файлов всегда указывайте кодировку.

Если файл большой (например, лог-файл на 500 МБ), читать его целиком — плохая идея. Тут на помощь приходят потоки (streams):

import { createReadStream } from 'fs';
import { createInterface } from 'readline';

async function readLargeFile() {
  const stream = createReadStream('bigfile.log', 'utf-8');
  const rl = createInterface({ input: stream });

  for await (const line of rl) {
    console.log('Строка:', line);
    // Обрабатываем каждую строку по мере чтения
  }
}

readLargeFile();

Запись в файл: создаём и дописываем

Запись бывает двух типов: перезапись (write) и добавление (append). Разница очевидна: write сотрёт старый файл, append добавит в конец.

import { writeFile, appendFile } from 'fs/promises';

// Перезапись файла
await writeFile('config.json', JSON.stringify({ theme: 'dark' }));

// Добавление строки в лог
await appendFile('app.log', `[${new Date().toISOString()}] Сервер запущен\n`);

Обратите внимание: если файла не существует, writeFile создаст его, а appendFile — тоже создаст, если его нет. Удобно, правда?

Управление файловой системой: создаём, перемещаем, удаляем

Файлы — это не только чтение и запись. Иногда нужно создать папку, переименовать файл или проверить, существует ли он.

Проверка существования файла

Многие новички делают так:

if (fs.existsSync('file.txt')) { ... }

Это работает, но синхронно — блокирует поток. Лучше использовать асинхронный вариант:

import { access } from 'fs/promises';
import { constants } from 'fs';

async function fileExists(path) {
  try {
    await access(path, constants.F_OK);
    return true;
  } catch {
    return false;
  }
}

Создание и удаление директорий

import { mkdir, rmdir, unlink } from 'fs/promises';

// Создать папку (рекурсивно — создаст все вложенные)
await mkdir('logs/2024/01', { recursive: true });

// Удалить пустую папку
await rmdir('old_data');

// Удалить файл
await unlink('temp.txt');

Важно: rmdir не удалит непустую папку. Для рекурсивного удаления используйте rm (доступен с Node.js 14.14):

import { rm } from 'fs/promises';
await rm('old_project', { recursive: true, force: true });

Перемещение и переименование

import { rename } from 'fs/promises';

// Переименовать файл
await rename('old_name.txt', 'new_name.txt');

// Переместить в другую папку
await rename('source/file.txt', 'backup/file_backup.txt');

Работа с путями: чтобы не было «сломанных» ссылок

Одна из самых частых ошибок — жёстко закодированные пути. На Windows пути с обратной косой чертой (\), на Linux — с прямой (/). Используйте модуль path:

import { join, resolve, basename, extname } from 'path';

const configPath = join(__dirname, 'config', 'app.json');
// Результат: /home/user/project/config/app.json (в Linux)

const fullPath = resolve('data', '..', 'config', 'settings.json');
// resolve нормализует путь: ../ означает подняться на уровень выше                

Блоги

Рекомендаций по книгам