WritableStream: примеры (JAVASCRIPT)

Использование WritableStream в JavaScript на практике
Раздел: Streams API, Потоки записи
WritableStream(underlyingSink (object), strategy (object)): WritableStream

Основы WritableStream

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

Конструктор принимает необязательный объект с тремя методами:

  • start(controller) - инициализация, может быть асинхронной
  • write(chunk, controller) - обработка каждого фрагмента данных
  • close(controller) - завершение записи
  • abort(reason) - прерывание с указанием причины

Объект возвращает экземпляр WritableStream со свойствами:

  • locked - boolean, указывает на возможность получения writer
  • getWriter() - метод для получения объекта WritableStreamDefaultWriter
  • close() - завершение потока
  • abort(reason) - прерывание потока

Базовые примеры

Простейший пример создания потока для записи строк:

const writableStream = new WritableStream({
  write(chunk) {
    console.log('Записано:', chunk);
  },
  close() {
    console.log('Поток закрыт');
  }
});

const writer = writableStream.getWriter();
await writer.write('Первая порция данных');
await writer.write('Вторая порция данных');
await writer.close();
Записано: Первая порция данных
Записано: Вторая порция данных
Поток закрыт

Пример с асинхронной записью:

const stream = new WritableStream({
  async write(chunk) {
    await new Promise(resolve => setTimeout(resolve, 100));
    console.log('Асинхронная запись:', chunk);
  }
});

const writer = stream.getWriter();
writer.write('Данные 1').then(() => {
  console.log('Запись завершена');
});
Асинхронная запись: Данные 1
Запись завершена

Альтернативные подходы в JavaScript

Для записи данных существуют несколько альтернатив:

  • Blob и FileWriter API - работа с файлами в браузере, требует поддержки File System Access API
  • TransformStream - преобразование данных при записи, часто используется совместно с WritableStream
  • Response.body - запись в HTTP-ответ, специализированное применение
  • Node.js Streams - в среде Node.js доступны более традиционные потоковые API

WritableStream предпочтительнее для современных веб-приложений, работающих с большими данными, в то время как FileWriter подходит для операций с файловой системой.

Аналоги в других языках

Python предоставляет аналогичную функциональность:

# Python
async def write_stream():
    class CustomWriter:
        async def write(self, data):
            print(f'Записано: {data}')
    
    writer = CustomWriter()
    await writer.write('пример данных')

В PHP используются ресурсы потоков:

// PHP
$stream = fopen('php://output', 'w');
fwrite($stream, 'данные для записи');
fclose($stream);

C предлагает низкоуровневые операции:

// C
FILE *stream = fopen('file.txt', 'w');
fwrite(data, sizeof(char), strlen(data), stream);
fclose(stream);

Основное отличие JavaScript реализации - асинхронная природа и интеграция с современными веб-API.

Распространенные ошибки

Попытка получения writer при заблокированном потоке:

const stream = new WritableStream({
  write(chunk) {}
});

const writer1 = stream.getWriter();
const writer2 = stream.getWriter(); // Ошибка
TypeError: Cannot get a writer on a locked WritableStream

Необработанные ошибки при записи:

const stream = new WritableStream({
  write(chunk) {
    throw new Error('Ошибка записи');
  }
});

const writer = stream.getWriter();
writer.write('данные').catch(e => {
  console.error('Перехвачено:', e.message);
});
Перехвачено: Ошибка записи

Использование закрытого потока:

const writer = stream.getWriter();
await writer.close();
await writer.write('данные'); // Ошибка
TypeError: Cannot write to a closed WritableStream

История изменений

Спецификация WritableStream развивается вместе со Streams API:

  • 2015 - первоначальная спецификация в рамках WhatWG
  • 2017 - добавление поддержки TransformStream
  • 2019 - интеграция с File System Access API
  • 2021 - улучшения производительности и стабильности
  • 2023 - расширенная поддержка TypeScript типов

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

Расширенные примеры

Запись в несколько потоков одновременно:

Пример javascript
async function teeStream(data) {
  const primaryStream = new WritableStream({
    write(chunk) {
      console.log('Основной поток:', chunk);
    }
  });
  
  const secondaryStream = new WritableStream({
    write(chunk) {
      console.log('Дополнительный поток:', chunk);
    }
  });
  
  const writer1 = primaryStream.getWriter();
  const writer2 = secondaryStream.getWriter();
  
  for (const item of data) {
    await Promise.all([
      writer1.write(item),
      writer2.write(item.toUpperCase())
    ]);
  }
  
  await writer1.close();
  await writer2.close();
}

teeStream(['a', 'b', 'c']);
Основной поток: a
Дополнительный поток: A
Основной поток: b
Дополнительный поток: B
Основной поток: c
Дополнительный поток: C

Контроль backpressure с помощью queueing strategy:

Пример javascript
const highWaterMark = 2;
const stream = new WritableStream({
  write(chunk) {
    return new Promise(resolve => {
      setTimeout(() => {
        console.log('Обработано:', chunk);
        resolve();
      }, 500);
    });
  }
}, new CountQueuingStrategy({ highWaterMark }));

const writer = stream.getWriter();

for (let i = 1; i <= 5; i++) {
  const ready = writer.ready;
  console.log(`Запись ${i}, готовность: ${await ready}`);
  writer.write(`Элемент ${i}`);
}
Запись 1, готовность: undefined
Запись 2, готовность: true
Обработано: Элемент 1
Запись 3, готовность: true
Обработано: Элемент 2
...

Интеграция с File System Access API:

Пример javascript
async function saveFile() {
  const fileHandle = await window.showSaveFilePicker();
  const writable = await fileHandle.createWritable();
  
  const stream = new WritableStream({
    write(chunk) {
      return writable.write(chunk);
    },
    close() {
      return writable.close();
    },
    abort() {
      return writable.abort();
    }
  });
  
  const writer = stream.getWriter();
  await writer.write('Содержимое файла\n');
  await writer.close();
}

JS WritableStream function comments

En
WritableStream Represents a writable stream of data