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