Работа с файлами в JavaScript ES2026: гид по новинкам
Размер текста: A+ A-

Работа с файлами в JavaScript ES2026: гид по новинкам

Нажмите, чтобы оценить наш труд:
[Всего: 1 Средняя: 5]

Современная экосистема JavaScript кардинально упростила манипуляции с файловыми объектами как в браузере, так и при взаимодействии с серверной инфраструктурой.

Нативный синтаксис современных стандартов ECMAScript упрощает работу с бинарными данными и асинхронными ресурсами, а в ряде сценариев позволяет обходиться без громоздких сторонних библиотек. Разработка веб-приложений теперь опирается на прямые интерфейсы файловой системы, безопасные механизмы явного управления ресурсами и быстрые серверные обработчики на PHP.

Смотри также тут прочие материалы по теме:

Что изменилось в ES2026 в плане работы с файлами

В спецификацию ECMAScript 2026 вошли стандартизированные механизмы работы с бинарными структурами и асинхронными ресурсами, значительно упростившие некоторые файловые операции.

Одним из важных нововведений стало стандартизированное явное управление ресурсами (Explicit Resource Management) с помощью оператора using и await using. Эти конструкции позволяют автоматически освобождать ресурсы при выходе из области видимости, если соответствующий объект реализует механизмы Symbol.dispose или Symbol.asyncDispose. Это помогает избежать ошибок, связанных с забытым освобождением поддерживающих данный механизм ресурсов, хотя не означает автоматического управления всеми файловыми потоками и не исключает утечки памяти в целом.

Второе ключевое улучшение касается базовых методов стандартных классов Uint8Array и ArrayBuffer. В современных реализациях JavaScript доступны встроенные высокопроизводительные методы кодирования и декодирования Uint8Array.fromBase64(), Uint8Array.prototype.toBase64(), а также передача содержимого буфера через метод transfer(). Это позволяет отказаться от некоторых медленных преобразований на базе FileReader или сторонних конвертеров при подготовке локальных файлов к бинарной передаче или конвертации изображений.

Ниже представлен практический пример использования нового синтаксиса await using вместе с прямым преобразованием бинарных данных файла без необходимости вручную закрывать ресурс, при условии что используемый файловый объект поддерживает Symbol.asyncDispose.

async function processBinaryFile(fileHandle) {
// Автоматическое освобождение ресурса при выходе из функции,
// если объект поддерживает Symbol.asyncDispose
await using writableStream = await fileHandle.createWritable();

const rawBase64 = "SGVsbG8gV29ybGQgRVMyMDI2IEZpbGUgQVBJ";

// Нативное декодирование Base64 напрямую в байтовый массив
const bytes = Uint8Array.fromBase64(rawBase64);

await writableStream.write(bytes);

// Ресурс будет автоматически освобожден при выходе из области действия,
// если writableStream поддерживает Symbol.asyncDispose
}

Создание, запись, дозапись и сохранение файла на ПК

Локальное управление файлами на стороне клиента в современной веб-разработке строится на базе спецификации File System Access API.

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

Процесс создания и первоначальной записи файла начинается с вызова системного проводника через метод showSaveFilePicker. После выбора пути браузер возвращает дескриптор FileSystemFileHandle, из которого извлекается поток записи FileSystemWritableFileStream. Данные записываются в файловый поток чанками или единым массивом, после чего поток необходимо закрыть, чтобы завершить операцию записи и сохранить изменения в файле.

Для дозаписи информации в уже существующий файл используется похожий алгоритм, но с преднамеренным смещением курсора записи. Разработчик запрашивает у пользователя существующий файл через showOpenFilePicker, получает доступ к потоку записи с параметром keepExistingData: true и вызывает метод seek(), передавая в него текущий размер файла. Это позволяет установить позицию записи в конце существующих данных и добавить новую информацию, не загружая содержимое всего файла в память JavaScript.

При этом keepExistingData: true обычно требует сохранения существующих данных во временном файле, поэтому такой способ не следует рассматривать как гарантированную операцию прямой дозаписи непосредственно в физический файл.

В представленном коде показан полный цикл работы: инициализация файла, стандартная запись и последующая дозапись текстовых логов с сохранением на диске.

// 1. Создание и первичная запись в новый файл
async function createAndSaveFile() {
const options = {
suggestedName: 'log-2026.txt',
types: [{
description: 'Текстовые файлы',
accept: { 'text/plain': ['.txt'] },
}],
};

const handle = await window.showSaveFilePicker(options);
const writable = await handle.createWritable();

await writable.write('Запись первой строки: системная инициализация.\n');
await writable.close();

return handle;
}

// 2. Дозапись данных в конец существующего файла
async function appendToFile(handle) {
const file = await handle.getFile();
const writable = await handle.createWritable({
keepExistingData: true
});

// Смещение курсора в конец существующих данных
await writable.seek(file.size);

await writable.write(
`Дозапись новой строки: ${new Date().toISOString()}\n`
);

await writable.close();
}

Также учти, что File System Access API требует защищённого контекста (обычно HTTPS), а вызов picker должен происходить в результате действия пользователя.

Передача и обработка файлов на сервере под управлением PHP

Когда задачи веб-приложения выходят за рамки браузера, возникает необходимость отправки локально сгенерированных или измененных файлов на сервер.

На стороне клиента JavaScript собирает бинарные данные, объекты Blob или File в контейнер FormData и передает их с помощью асинхронного запроса fetch. При передаче FormData браузер самостоятельно формирует заголовок Content-Type: multipart/form-data с необходимым boundary, поэтому устанавливать этот заголовок вручную не требуется.

На серверной стороне интерпретатор PHP принимает входящий запрос и помещает загруженный файл во временную директорию, а информация о нём становится доступна через массив $_FILES. Для сохранения нового загруженного файла используется стандартная функция move_uploaded_file, а для записи данных, уже находящихся в памяти или полученных другим способом, можно использовать file_put_contents или низкоуровневые вызовы fopen и fwrite. Если перед разработчиком стоит задача дописать информацию в имеющийся на сервере лог или текстовый документ, в функцию file_put_contents передается специальный флаг FILE_APPEND, который предотвращает стирание предыдущего содержимого. При работе с большими файлами следует избегать загрузки всего содержимого в память и использовать потоковую обработку.

Особое внимание при серверной обработке уделяется безопасности и целостности структуры. PHP-скрипт обязан проверять корректность передаваемых данных, код ошибки загрузки, ограничивать максимальный размер принимаемого файла и очищать имя файла от спецсимволов. Ошибки при проверке входящих параметров могут привести к перезаписи системных файлов или исполнению вредоносного кода в рабочей директории сервера.

Ниже приведен комплект из двух сценариев: клиентский JavaScript-код для отправки данных и серверный скрипт PHP, принимающий запрос и выполняющий запись и дозапись на сервере.

// Client-side: JavaScript (ES2026)
async function sendFileToServer(fileBlob, filename, isAppend = false) {
const formData = new FormData();

formData.append('file_data', fileBlob, filename);
formData.append('mode', isAppend ? 'append' : 'overwrite');

const response = await fetch('/api/upload.php', {
method: 'POST',
body: formData
});

const result = await response.json();
console.log(result.status);
}
<?php
// Server-side: PHP (upload.php)
header('Content-Type: application/json');

if ($_SERVER['REQUEST_METHOD'] !== 'POST' || empty($_FILES['file_data'])) {
echo json_encode([
'status' => 'error',
'message' => 'Неверный запрос'
]);
exit;
}

if ($_FILES['file_data']['error'] !== UPLOAD_ERR_OK) {
echo json_encode([
'status' => 'error',
'message' => 'Ошибка загрузки файла'
]);
exit;
}

$uploadDir = __DIR__ . '/storage/';

if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}

$fileName = basename($_FILES['file_data']['name']);
$fileName = preg_replace('/[^a-zA-Z0-9_.-]/', '_', $fileName);

if ($fileName === '') {
echo json_encode([
'status' => 'error',
'message' => 'Некорректное имя файла'
]);
exit;
}

$targetPath = $uploadDir . $fileName;
$mode = $_POST['mode'] ?? 'overwrite';
$tempPath = $_FILES['file_data']['tmp_name'];

if ($mode === 'append' && file_exists($targetPath)) {
// Дозапись в существующий файл
$content = file_get_contents($tempPath);

if ($content === false) {
echo json_encode([
'status' => 'error',
'message' => 'Не удалось прочитать загруженный файл'
]);
exit;
}

if (file_put_contents($targetPath, $content, FILE_APPEND | LOCK_EX) === false) {
echo json_encode([
'status' => 'error',
'message' => 'Не удалось дописать файл'
]);
exit;
}
} else {
// Перезапись или создание нового файла
if (!move_uploaded_file($tempPath, $targetPath)) {
echo json_encode([
'status' => 'error',
'message' => 'Не удалось сохранить файл'
]);
exit;
}
}

echo json_encode([
'status' => 'success',
'filename' => $fileName
]);

Но здесь у нас есть один нюанс: ветка append здесь всё ещё читает загруженный файл целиком через file_get_contents(). Для обычных текстовых логов это нормально, но для действительно больших файлов лучше сделать потоковую дозапись через fopen()/fwrite(), чтобы не загружать весь файл в память.

Генерация на JS и скачивание файлов на лету без сохранения

Динамическое создание документов без участия серверного диска или базы данных — эффективный паттерн для формирования чеков, отчетов и печатных форм.

Суть метода заключается в полной сборке бинарной структуры документа в оперативной памяти браузера с последующей передачей полученного массива в специальный объектный URL-адрес. Такой подход снижает нагрузку на серверную инфраструктуру, гарантирует приватность пользовательских данных и обеспечивает мгновенный отклик интерфейса.

Для генерации сложных форматированных документов, таких как файлы PDF, в клиентском JavaScript выстраивается валидная двоичная структура спецификации с заголовками, объектами страниц, текстовыми потоками и таблицей смещений (xref). После завершения сборки полученный двоичный буфер оборачивается в объект Blob с указанием соответствующего MIME-типа application/pdf.

Скачивание сформированного документа запускается программным путем. В DOM-дереве создается невидимый элемент гиперссылки, атрибуту href которого присваивается временный URL-адрес, сгенерированный методом URL.createObjectURL(). После имитации клика по ссылке браузер начинает загрузку файла на устройство пользователя. После того как временный URL больше не нужен, разработчик освобождает связанный с ним ресурс вызовом URL.revokeObjectURL().

В следующем примере продемонстрирован полный рабочий код генерации валидного PDF-документа минимального размера на лету с помощью чистого JavaScript и его скачивания без сохранения на сервере. Для сохранения простоты примера в PDF используется текст из ASCII-символов; полноценная поддержка русского текста требует соответствующего шрифта и его встраивания в PDF.

function generateAndDownloadPDF() {
const textContent = “Report generated on the fly in JavaScript (ES2026)”;

// Текстовый поток PDF
const contentStream =
`BT
/F1 24 Tf
100 300 Td
(${textContent}) Tj
ET
`;

const encoder = new TextEncoder();

// Количество байтов содержимого потока
const contentLength = encoder.encode(contentStream).length;

// Объекты PDF
const objects = [
`1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
`,

`2 0 obj
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
endobj
`,

`3 0 obj
<< /Type /Page /Parent 2 0 R
/Resources <<
/Font <<
/F1 5 0 R
>>
>>
/MediaBox [0 0 600 400]
/Contents 4 0 R
>>
endobj
`,

`4 0 obj
<< /Length ${contentLength} >>
stream
${contentStream}endstream
endobj
`,

`5 0 obj
<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>
endobj
`
];

// Заголовок PDF
let pdf = "%PDF-1.4\n";

// Смещения объектов от начала PDF-файла
const offsets = [0];

for (const object of objects) {
offsets.push(encoder.encode(pdf).length);
pdf += object;
}

// Позиция таблицы xref
const xrefOffset = encoder.encode(pdf).length;

// Таблица перекрестных ссылок
pdf += "xref\n";
pdf += "0 6\n";
pdf += "0000000000 65535 f \n";

for (let i = 1; i < offsets.length; i++) {
pdf += `${String(offsets[i]).padStart(10, "0")} 00000 n \n`;
}

// Trailer и завершение PDF
pdf +=
`trailer
<< /Size 6 /Root 1 0 R >>
startxref
${xrefOffset}
%%EOF`;

// Создание бинарного Blob-объекта в оперативной памяти
const pdfBytes = encoder.encode(pdf);

const blob = new Blob([pdfBytes], {
type: "application/pdf"
});

// Генерация временной ссылки на объект в памяти
const downloadUrl = URL.createObjectURL(blob);

// Создание и программный клик по ссылке для скачивания
const link = document.createElement("a");
link.href = downloadUrl;
link.download = `document_${Date.now()}.pdf`;

document.body.appendChild(link);
link.click();
document.body.removeChild(link);

// Освобождение object URL после запуска скачивания
setTimeout(() => {
URL.revokeObjectURL(downloadUrl);
}, 1000);
}

Итак, по коду выше видно, что у нас задаётся текст:

const textContent = "Report generated on the fly in JavaScript (ES2026)";

Формируется PDF-команда вывода текста:

const contentStream =
`BT
/F1 24 Tf
100 300 Td
(${textContent}) Tj
ET
`;

Это уже не JavaScript, а команды внутреннего языка PDF. Они говорят PDF-читалке, как разместить текст на странице. Это PDF Content Stream — маленький язык команд, которым PDF описывает, что нужно нарисовать на странице. Описание оного является отдельной темой для обсуждения в статье.

Создаётся TextEncoder:

const encoder = new TextEncoder();

Он нужен, чтобы переводить JavaScript-строку в UTF-8 байты. PDF работает с байтовыми смещениями, поэтому String.length здесь недостаточно.

Вычисляется размер текстового потока в байтах:

const contentLength = encoder.encode(contentStream).length;

Это значение записывается в “/Length …”, чтобы PDF знал точный размер содержимого stream.

Создаются объекты PDF. В массиве objects находятся пять объектов:

  • 1 0 obj — корневой объект PDF (Catalog);
  • 2 0 obj — список страниц;
  • 3 0 obj — описание самой страницы;
  • 4 0 obj — поток команд, который рисует текст;
  • 5 0 obj — описание шрифта Helvetica.

Ну и так далее…

Надеюсь, тема раскрыта.

Дополнительные возможности JavaScript для работы с файлами

Помимо рассмотренных ранее File System Access API, Blob, File, FormData, URL.createObjectURL() и механизмов управления ресурсами, современный JavaScript предоставляет дополнительные средства для обработки файловых данных.

Для работы с большими файлами особенно важны ReadableStream и WritableStream, позволяющие обрабатывать данные частями без необходимости загружать весь файл в оперативную память. Также можно использовать TextEncoder и TextDecoder для преобразования текста в бинарные данные и обратно, ArrayBuffer и TypedArray для низкоуровневой работы с бинарным содержимым, а fetch() позволяет передавать File, Blob, ArrayBuffer и другие типы данных непосредственно на сервер.

Одним из практических сценариев является передача XML-файла на сервер для обработки PHP с последующим получением структурированного JSON-ответа. Если XML уже представлен в JavaScript в виде строки, его можно отправить обычным POST-запросом с типом application/xml. PHP получает содержимое запроса через php://input, обрабатывает XML и возвращает JSON, который JavaScript получает через response.json().

Если XML находится в объекте File, его можно передать через fetch() непосредственно, без преобразования в FormData.

async function sendXMLToPHP(xml) {
const response = await fetch('/api/process.php', {
method: 'POST',
headers: {
'Content-Type': 'application/xml; charset=UTF-8'
},
body: xml
});

if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}

const result = await response.json();

console.log('Ответ PHP:', result);

return result;
}


// Пример XML
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<request>
<user>
<id>123</id>
<name>John</name>
</user>
<action>process</action>
</request>`;

sendXMLToPHP(xml)
.then(result => {
console.log(result);
})
.catch(error => {
console.error('Ошибка:', error);
});

И PHP часть:

<?php

header('Content-Type: application/json; charset=UTF-8');

// Получаем XML из тела POST-запроса
$xmlData = file_get_contents('php://input');

if ($xmlData === false || trim($xmlData) === '') {
http_response_code(400);

echo json_encode([
'status' => 'error',
'message' => 'XML не передан'
], JSON_UNESCAPED_UNICODE);

exit;
}

// Разбираем XML
$xml = simplexml_load_string($xmlData);

if ($xml === false) {
http_response_code(400);

echo json_encode([
'status' => 'error',
'message' => 'Некорректный XML'
], JSON_UNESCAPED_UNICODE);

exit;
}

// Получаем данные из XML
$userId = (string) $xml->user->id;
$userName = (string) $xml->user->name;
$action = (string) $xml->action;

// Обрабатываем полученные данные
$result = [
'user_id' => $userId,
'user_name' => $userName,
'action' => $action,
'processed' => true
];

// Возвращаем JSON
echo json_encode([
'status' => 'success',
'data' => $result
], JSON_UNESCAPED_UNICODE);

Здесь схема взаимодействия выглядит так:

JavaScript → XML → fetch() → PHP → обработка XML → JSON → JavaScript

При этом XML не обязательно должен быть строкой. Если он уже находится в файле, выбранном пользователем, объект File можно передать непосредственно в fetch():

const response = await fetch('/api/process.php', {
method: 'POST',
headers: {
'Content-Type': 'application/xml'
},
body: file
});

const result = await response.json();

Это позволяет передавать файл на сервер без промежуточного преобразования в FormData, если сервер ожидает непосредственно XML в теле HTTP-запроса.

Работа на JS с файлами на ПК пользователя

Схема следующая:

Пользователь
↓
Выбирает папку
↓
FileSystemDirectoryHandle
↓
getFileHandle()
↓
createWritable()
↓
write()
↓
close()
↓
Файл на диске

Нельзя сделать так:

const file = openFile('C:\\Users\\User\\Documents\\secret.txt');

и получить доступ к произвольному файлу пользователя.

Правильная модель браузера:

JavaScript
↓
системный диалог
↓
пользователь выбирает файл/папку
↓
браузер выдаёт Handle
↓
JavaScript работает только с разрешённым ресурсом

То есть JavaScript действительно может работать с реальными файлами на ПК пользователя, включая чтение, создание, изменение и дозапись, но доступ предоставляется браузером только в пределах ресурсов, явно выбранных и разрешённых пользователем.

Нажмите, чтобы оценить наш труд:
[Всего: 1 Средняя: 5]
Ethan Carter

Я, Итан Картер – американский разработчик и технический автор с более чем 20-летним опытом в системном и прикладном программировании. Мой основной профиль — низкоуровневая разработка на Assembler: 22 года практики, включая глубокую работу с оптимизацией кода, архитектурой процессоров и производительностью критичных по скорости решений. Я защитил PhD dissertation по Assembler, а также более 18 лет работаю с ASP.NET, создавая корпоративные веб-системы, API и масштабируемые backend-решения.

Дополнительно я имею 9 лет опыта в C++ и C#, а также 7 лет практики программирования микроконтроллеров на Assembler. Благодаря моему сочетанию академической подготовки и прикладного инженерного опыта я могу писать статьи на стыке архитектуры ПО, низкоуровневой оптимизации и современной разработки, делая сложные технические темы понятными для профессиональной аудитории.

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *


Срок проверки reCAPTCHA истек. Перезагрузите страницу.

О нас | Контакты


Прокрутить вверх