Stream API を使いこなせるようになりたくて、文字列操作を例に試してみた。長いテキストを特定サイズの配列に分割する TransformStream を実装する。
実装
type Ctrl = TransformStreamDefaultController<string[]>;
class TextArrayTransformStream extends TransformStream<string, string[]> { #chunk: string[] = []; #chunkSize: number; #splitReg: RegExp;
constructor(chunkSize: number, maxTextLength: number) { super({ transform: (chunk, controller) => this.#handle(chunk, controller), flush: (controller) => this.#flush(controller), }); this.#chunkSize = chunkSize; this.#splitReg = new RegExp(`.{1,${maxTextLength}}`, "g"); }
#handle(chunk: string, controller: Ctrl): void { for (const str of chunk.match(this.#splitReg) || []) { if (this.#chunk.length >= this.#chunkSize) { controller.enqueue(this.#chunk); this.#chunk = []; } else { this.#chunk.push(str); } } }
#flush(controller: Ctrl): void { if (this.#chunk.length > 0) { controller.enqueue(this.#chunk); } }}補助関数
function toReadableStream(text: string): ReadableStream<string> { return new ReadableStream({ start(controller) { controller.enqueue(text); controller.close(); } });}使用例
async function main() { const text = "長いテキスト..."; const arrayLength = 5; // 5個ずつ配列にまとめる const textLength = 10; // 各要素は10文字
const stream = toReadableStream(text) .pipeThrough(new TextArrayTransformStream(arrayLength, textLength));
const reader = stream.getReader(); while (true) { const { done, value } = await reader.read(); if (done) break;
console.log(value); // string[] が順次出力される }}ユースケース
このパターンは以下の場面で有効だ:
- LLM API のレスポンスをバッチ処理する時
- 大容量テキストをページング表示する時
- 文字数制限付き API に送信する前の前処理
AsyncIterator は実装していないので、for await…of 構文は使えない点に注意。
hsb.horse