logo hsb.horse
← 스니펫 목록으로 돌아가기

Snippets

TransformStream으로 텍스트 분할 처리

ReadableStream과 TransformStream을 사용하여 긴 텍스트를 지정된 크기의 청크로 분할하는 TypeScript 구현 예제.

게시일: 수정일:

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 구문은 사용할 수 없는 점에 유의.

실무 메모

이 스니펫은 TypeScript, JavaScript, Stream 주변에서 같은 조작이나 판정을 매번 다시 쓰고 싶지 않을 때 잘 맞는다. 작은 보조 단위로 빼 두면 호출부에서는 의도만 읽기 쉬워진다.

반대로 분기와 전제조건이 늘어나 책임이 커진다면, 하나의 스니펫에 전부 넣지 않는 편이 낫다. 절차와 helper를 나누거나 역할별로 쪼개 두는 편이 유지보수에 유리하다.