logo hsb.horse
← ブログ一覧に戻る

ブログ

値をReadableStreamに変換するTypeScriptユーティリティ関数

StringやオブジェクトなどをReadableStreamに変換するユーティリティ関数。Blob経由ではなく任意の型でストリームを扱う実装を整理。

公開日:

Stringなどのプリミティブな値やオブジェクトなどをReadableStreamに変換する場合、よく使う方法としては、一度Blobに変換する方法がよく用いられると思う。

ただ、Blobを経由してStreamを生成するとUint8Arrayに変換されてしまう。(バイナリ操作が一番効率がいいと思うので、別に誤っているとは思わない。)

Stringなどでも取り扱えるようにユーティリティ関数が欲しくなったので書いてみた。

コード

type StreamResource<T> = T extends Blob
? Uint8Array
: T extends Uint8Array | ArrayBuffer
? Uint8Array
: T extends undefined | null | symbol
? never
: T;
function toReadableStream<T>(value: T): ReadableStream<StreamResource<T>> {
if (value === undefined || value === null || typeof value === "symbol") {
throw new TypeError(
"Cannot convert undefined, null, or symbol to ReadableStream"
);
}
if (value instanceof Blob) {
return value.stream() as ReadableStream<StreamResource<T>>;
}
if (value instanceof Uint8Array || value instanceof ArrayBuffer) {
const uint8Array = new Uint8Array(value);
return new ReadableStream<Uint8Array>({
start(controller) {
controller.enqueue(uint8Array);
controller.close();
},
}) as ReadableStream<StreamResource<T>>;
}
return new ReadableStream<StreamResource<T>>({
start(controller) {
controller.enqueue(value as StreamResource<T>);
controller.close();
},
});
}

利用例

const blob = new Blob(['Hello, World!'], { type: 'text/plain' });
const blobStream = toReadableStream(blob);
// ^? ReadableStream<Uint8Array>
const buffer = new ArrayBuffer(8);
const bufferStream = toReadableStream(buffer);
// ^? ReadableStream<Uint8Array>
const uint8Array = new Uint8Array([1, 2, 3, 4]);
const uint8ArrayStream = toReadableStream(uint8Array);
// ^? ReadableStream<Uint8Array>
const str = 'Hello, TypeScript!';
const strStream = toReadableStream(str);
// ^? ReadableStream<string>
const num = 42;
const numStream = toReadableStream(num);
// ^? ReadableStream<number>
const undefStream = toReadableStream(undefined);
// ^? ReadableStream<never>

ポイント

  • Conditional Typesで型安全に変換
  • Blob/ArrayBufferはUint8Arrayストリームに
  • String/number等はそのままの型でストリーム化
  • undefined/null/symbolは型エラー

まとめ

ReadableStreamに直接値をenqueueすることで、Blob経由よりも柔軟なストリーム生成が可能になる。

型推論も効くため、TypeScriptで扱いやすいユーティリティ関数として活用できる。