JavaScript/WritableStream
WritableStreamオブジェクト
JavaScriptにおけるWritableStream
は、ストリームベースのデータ処理を可能にする重要なコンポーネントです。ストリーミングAPIの一部として、データの書き込み先として機能します。このオブジェクトを利用することで、大量のデータを効率的に処理できるようになります。
基本概念
WritableStream
は、データを一度に全て処理するのではなく、チャンク(小さな断片)単位で処理することができます。これにより、メモリ使用量を抑えながら大きなデータセットを扱うことが可能です。
WritableStream
の最も基本的な使い方を見てみましょう:
const writableStream = new WritableStream({ start(controller) { console.log('ストリームの開始'); }, write(chunk, controller) { console.log('データの書き込み:', chunk); }, close() { console.log('ストリームの終了'); }, abort(reason) { console.error('ストリームが中断されました:', reason); } });
WritableStreamのメソッド
WritableStream
オブジェクトには以下の主要なメソッドがあります:
// 新しいライターを取得 const writer = writableStream.getWriter(); // データを書き込む writer.write('こんにちは、ストリーム!').then(() => { console.log('データが書き込まれました'); }); // 書き込みを終了する writer.close().then(() => { console.log('ストリームが正常に閉じられました'); }); // 書き込みを中断する writer.abort('エラーが発生しました').then(() => { console.log('ストリームが中断されました'); });
WritableStreamDefaultWriterの使用
WritableStream
を操作するには、通常WritableStreamDefaultWriter
オブジェクトを使用します:
async function writeToStream() { const writer = writableStream.getWriter(); try { await writer.write('最初のチャンク'); await writer.write('2番目のチャンク'); await writer.write('最後のチャンク'); await writer.close(); } catch (error) { console.error('書き込み中にエラーが発生しました:', error); } finally { writer.releaseLock(); } } writeToStream();
WritableStreamの状態
WritableStream
は常に以下のいずれかの状態にあります:
状態 | 説明 |
---|---|
writable | ストリームが書き込み可能な状態 |
closing | ストリームが閉じている途中の状態 |
closed | ストリームが閉じられた状態 |
errored | エラーが発生した状態 |
状態の確認方法:
const writer = writableStream.getWriter(); // 各状態を確認するためのプロパティ console.log('準備完了?', !writer.closed); console.log('閉じている?', writer.closed); console.log('書き込み可能?', !writer.closed && !writer.desiredSize === null); // 非同期で状態の変化を待つ writer.closed.then(() => { console.log('ストリームが閉じられました'); });
実践的な例:ファイルをダウンロードして保存する
以下の例では、fetch
APIとWritableStream
を組み合わせて、ファイルをダウンロードし保存します:
async function downloadAndSave(url, filename) { // ファイルのダウンロード const response = await fetch(url); const reader = response.body.getReader(); // ファイルシステムアクセスAPIを使用 const fileHandle = await window.showSaveFilePicker({ suggestedName: filename, types: [{ description: 'テキストファイル', accept: {'text/plain': ['.txt']} }] }); // 書き込み用のストリームを作成 const writable = await fileHandle.createWritable(); // データをストリーミング while (true) { const {done, value} = await reader.read(); if (done) break; await writable.write(value); } // 書き込みを完了 await writable.close(); console.log('ファイルが保存されました'); } // 使用例 downloadAndSave('https://example.com/large-text-file.txt', 'downloaded-file.txt');
バックプレッシャーの処理
WritableStream
の重要な機能の一つに「バックプレッシャー」があります。これは、書き込み先が処理しきれないほど速くデータが送られてくる場合に、自動的にデータフローを調整する仕組みです:
async function writeWithBackpressure(writableStream, data) { const writer = writableStream.getWriter(); for (const chunk of data) { // write()はバックプレッシャーを自動的に処理するPromiseを返す await writer.write(chunk); console.log(`チャンク "${chunk}" が書き込まれました`); } await writer.close(); console.log('すべてのデータが書き込まれました'); } // 大量のデータを生成 const largeDataset = Array.from({length: 1000}, (_, i) => `データ項目 ${i}`); // カスタムストリームを作成(処理に時間がかかる操作をシミュレート) const slowWritableStream = new WritableStream({ write(chunk, controller) { return new Promise(resolve => { // 各チャンクの処理に100ミリ秒かかると仮定 setTimeout(() => { console.log(`処理: ${chunk}`); resolve(); }, 100); }); } }); // バックプレッシャーを使ってデータを書き込む writeWithBackpressure(slowWritableStream, largeDataset);
TransformStreamとの連携
WritableStream
はTransformStream
やReadableStream
と組み合わせて使用することで、強力なデータ処理パイプラインを構築できます:
// テキストを大文字に変換するTransformStream const uppercaseTransform = new TransformStream({ transform(chunk, controller) { // 文字列を大文字に変換 const uppercased = chunk.toUpperCase(); controller.enqueue(uppercased); } }); // 結果を表示するWritableStream const consoleWritable = new WritableStream({ write(chunk) { console.log('変換結果:', chunk); } }); // パイプラインを作成 fetch('https://example.com/data.txt') .then(response => { // 入力ストリームを変換ストリームにパイプし、さらに出力ストリームにパイプする const readableStream = response.body; // ReadableStream → TransformStream → WritableStream return readableStream .pipeThrough(uppercaseTransform) .pipeTo(consoleWritable); }) .then(() => console.log('処理が完了しました')) .catch(error => console.error('エラーが発生しました:', error));
ブラウザとNode.jsの違い
WritableStream
はブラウザとNode.jsの両方で利用できますが、使用方法に若干の違いがあります:
// ブラウザ環境 // Web Streams APIはグローバルに利用可能 const browserWritableStream = new WritableStream({...}); // Node.js環境(バージョン16以降) // 'stream/web'モジュールからインポートする必要がある const { WritableStream } = require('stream/web'); const nodeWritableStream = new WritableStream({...}); // または、ESモジュールを使用する場合 import { WritableStream } from 'stream/web'; const nodeWritableStream = new WritableStream({...});
まとめ
WritableStream
はJavaScriptのストリーミングAPIにおける重要なコンポーネントで、大量のデータを効率的に書き込むための手段を提供します。バックプレッシャーのサポートや他のストリームとの統合により、メモリ効率の良いデータ処理パイプラインの構築が可能になります。Web開発における大規模データ処理、ファイル操作、ネットワーク通信など、多くの場面で活用できる強力なツールです。