Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Web Streams APIの基本と実践、TypeScriptでの活用法 / TSKaig...

Web Streams APIの基本と実践、TypeScriptでの活用法 / TSKaigi 2025 Web Streams API

TSKaigi 2025 のセッション資料です。

セッション情報はこちら↓
https://2025.tskaigi.org/talks/tasshi

Avatar for tasshi

tasshi

May 23, 2025
Tweet

More Decks by tasshi

Other Decks in Technology

Transcript

  1. Web Streams APIの基本と実践、TypeScriptでの活用法 – TSKaigi 2025 #TSKaigi #TSKaigi2025 #tskaigi_leverages はじめに

    田代 雅治 (@tasshi_me) • 仕事 • サイボウズ株式会社 • kintone開発 拡張基盤 チーム EM • DX (Developer eXperience) デザイン • 週4勤務、週1で副業 • 主にnpmパッケージとサーバーサイドJS • (画面はあまり触らない) • 去年から会社の同期とバンド始めました 自己紹介 2
  2. Web Streams APIの基本と実践、TypeScriptでの活用法 – TSKaigi 2025 #TSKaigi #TSKaigi2025 #tskaigi_leverages はじめに

    • kintone ≒ かんたんに業務アプリを構築できるクラウドサービス • kintoneのプラグイン・連携サービス向けの開発基盤を開発・保守するチーム • API開発、SDK/CLIなどのOSS提供 • npmパッケージを多数メンテナンス kintone開発 拡張基盤 チーム 3
  3. Web Streams APIの基本と実践、TypeScriptでの活用法 – TSKaigi 2025 #TSKaigi #TSKaigi2025 #tskaigi_leverages はじめに

    Streams APIを触ったことがないという声を聞くので、ざっくりStreams APIの説明をします。 また、Node Streamとの違いや、Promiseベースの非同期処理との相互運用性についても話します。 Streams APIを試すきっかけとなると良いかなと思います。 そして、今後の実装の選択肢にStreams APIが追加されたらとても嬉しいです。 (TSKaigiですが、型の話は少ないかも) このセッションでは 4
  4. Web Streams APIの基本と実践、TypeScriptでの活用法 – TSKaigi 2025 #TSKaigi #TSKaigi2025 #tskaigi_leverages ストリームとは何か

    • データ入出力を逐次的に、効率よく扱うためのデータ構造 • データをより細かい分割された単位の連続した流れとして表現する • 昔からある概念 • 多くの開発言語でストリーム操作のインターフェースは提供されている • 古くはUNIXの標準ストリーム、pipe、redirectとか ストリームとは何か 6 変換処理A chunk chunk chunk 変換処理B chunk chunk chunk 変換処理C chunk chunk chunk chunk
  5. Web Streams APIの基本と実践、TypeScriptでの活用法 – TSKaigi 2025 #TSKaigi #TSKaigi2025 #tskaigi_leverages ストリームとは何か

    標準ストリームとパイプの例 7 cat input .txt sed “foo” tee Terminal output .txt “bar” “bar” “bar” “foo” stdout stdin stdin stdout stdout
  6. Web Streams APIの基本と実践、TypeScriptでの活用法 – TSKaigi 2025 #TSKaigi #TSKaigi2025 #tskaigi_leverages ストリームとは何か

    • メモリ空間を圧迫しにくい • データを処理する分だけメモリ上に展開するため • 大規模データや時間経過で無限に増大するデータの処理に有効 • 低遅延 • 先頭のデータが処理されるまでの時間が速い • ※最終的にデータ全体が処理されるまでの時間が早くなるとは限らない ストリームのメリット 8 処理A 処理B ストリーム処理の場合 バッチ処理の場合 時間経過 時間経過
  7. Web Streams APIの基本と実践、TypeScriptでの活用法 – TSKaigi 2025 #TSKaigi #TSKaigi2025 #tskaigi_leverages Streams

    API JavaScriptでストリーム処理を行うためのAPI • データは分割された断片(chunk)の連続した流れとして扱われる • 3種類の役割の異なるストリームオブジェクト • ストリームオブジェクト同士をパイプ接続(パイプチェーン)することで、 chunkは流れるように終端まで処理される Streams APIの概要 10 Transform Stream Readable Stream Writable Stream chunk データの読み込み データの変換 (読み込み+書き込み) データの書き込み chunk Source Sink
  8. Web Streams APIの基本と実践、TypeScriptでの活用法 – TSKaigi 2025 #TSKaigi #TSKaigi2025 #tskaigi_leverages Streams

    API 具体例: WebリソースをFetchしてファイルに保存 11 Text Decoder Stream Response. body FileSystem Writable FileStream string Uint8 Array example .com File System
  9. Web Streams APIの基本と実践、TypeScriptでの活用法 – TSKaigi 2025 #TSKaigi #TSKaigi2025 #tskaigi_leverages Streams

    API 基となるソース(underlying source)から流れるデータを表現するオブジェクト ソースから流れるデータをチャンクに分割し、ストリーム処理できる形で提供する • 基となるソース:ファイルシステム、ネットワークリソース、メモリ上の配列、など • Pull型/Push型のソースがある(付録を参照) • ReadableStreamの例:Fetch APIのResponse.body、Blob.stream() ReadableStream (読み取り可能なストリーム) 12 Readable Stream chunk Source chunk 読み出し raw data
  10. Web Streams APIの基本と実践、TypeScriptでの活用法 – TSKaigi 2025 #TSKaigi #TSKaigi2025 #tskaigi_leverages Streams

    API ストリームに流れるデータをある形式から別の形式に変換するオブジェクト • TransformStreamの例 • TextEncoderStream / TextDecoderStream: バイナリ 文字列の変換 • CompressionStream / DecompressionStream: データの圧縮・展開 (gzip, deflate) TransformStream (変換ストリーム) 13 chunk (string) chunk (Uint8Array) chunk (Uint8Array) TransformStream chunk (string)
  11. Web Streams APIの基本と実践、TypeScriptでの活用法 – TSKaigi 2025 #TSKaigi #TSKaigi2025 #tskaigi_leverages Streams

    API 基となるシンク(underlying sink)に流れるデータを表現するオブジェクト • 基となるシンク:ファイルシステム、データベース、など • WritableStreamの例:File System Access APIのFileSystemWritableFileStream WritableStream (書き込み可能なストリーム) 14 Writable Stream chunk Sink chunk 書き込み data
  12. Web Streams APIの基本と実践、TypeScriptでの活用法 – TSKaigi 2025 #TSKaigi #TSKaigi2025 #tskaigi_leverages Streams

    API • ReadableStream、TransformStream、 WritableStreamを継承して独自のストリームを作成する • データ処理用のいくつかのメソッドを実装する(pull, write, transform, etc.) • チャンクの型はGenericsで指定 • 後述のキューイング戦略もコンストラクタで指定 • DBアクセスとか、データエンコーディングとか、独自の処理を実装できる 独自のストリームオブジェクトを作成する 15
  13. Web Streams APIの基本と実践、TypeScriptでの活用法 – TSKaigi 2025 #TSKaigi #TSKaigi2025 #tskaigi_leverages Streams

    API • Stream同士をパイプ接続すると、chunkは流れるように終端まで処理される(パイプチェーン) • パイプ接続メソッド • Readable.pipeTo(): 終端のWritableStreamに接続 • Readable.pipeThrough(): 中間のTransformStreamに接続 パイプチェーン 16 Transform Stream Readable Stream Writable Stream chunk chunk Source Sink pipeThrough() pipeTo()
  14. Web Streams APIの基本と実践、TypeScriptでの活用法 – TSKaigi 2025 #TSKaigi #TSKaigi2025 #tskaigi_leverages Streams

    API • 1つのReadableStreamを2つのReadableStream(branch)に分配 • 分配したストリームはそれぞれ異なる速度で読み取ることができる • FetchしたデータをUIとキャッシュの両方に出力したりできる • 注:内部キューにチャンクが滞留するため、長すぎるデータストリームには適さない(付録を参照) ストリームの分配 (tee) 17 Readable Stream 元のストリーム Readable Stream 分配後のストリーム1 Source Readable Stream 分配後のストリーム2
  15. Web Streams APIの基本と実践、TypeScriptでの活用法 – TSKaigi 2025 #TSKaigi #TSKaigi2025 #tskaigi_leverages 利用イメージ

    • メモリ負荷を軽減できる例 • 他サービスからAPI経由で全レコードを取得し、加工して自サービスのDBに書き込んでいく • APIのレスポンスはJSONL形式 1. 他サービスからのデータインポート 19 Text Decoder Stream Response JSON Parser Stream Line Splitter Stream 行ごとに 再分割 (Transform) UTF-8 デコード (Tranform) JSONから オブジェクトに変換 (Transform) レスポンスの 読み込み (Readable) Writable Stream DBに 書き込み (Writable) fetch DB
  16. Web Streams APIの基本と実践、TypeScriptでの活用法 – TSKaigi 2025 #TSKaigi #TSKaigi2025 #tskaigi_leverages 利用イメージ

    • リアルタイム性を実現できる例 • API経由でGPTの回答を取得し、画面に表示する • デフォルトの一括応答では、回答全体が生成されてからレスポンスが返却される • ストリーミング応答を有効化すると、回答が生成された分ずつ返却されるようになる 2. GPTの回答をリアルタイム表示 20
  17. Web Streams APIの基本と実践、TypeScriptでの活用法 – TSKaigi 2025 #TSKaigi #TSKaigi2025 #tskaigi_leverages 内部キューと背圧

    • ストリーム(オブジェクト)は未処理チャンクを保持する内部キューを持っている • パイプチェーン上のストリーム間で処理速度に差がある場合、 内部キューにチャンクが溜まっていくことになる => メモリを圧迫する? ストリームオブジェクト間の処理速度の違いとメモリ使用量 22 Transform Stream Readable Stream Writable Stream chunk chunk チャンクが溜まっていく? ここの処理が遅い場合 (DBアクセスなど) ※TransformStream は 書込側・読出側それぞれに 内部キューを持つが省略
  18. Web Streams APIの基本と実践、TypeScriptでの活用法 – TSKaigi 2025 #TSKaigi #TSKaigi2025 #tskaigi_leverages 内部キューと背圧

    • チャンクを受け入れられない場合、ストリームは上流に停止信号を出す • 停止信号を受けた上流のストリームはデータの送信を停止する • 下流のストリームが送信を指示(pull)すると再び処理が再開する => チャンクの流速が調整されてメモリを圧迫せずに処理できる 背圧 (Backpressure) 23 Transform Stream Readable Stream Writable Stream chunk chunk 内部キューが満杯 STOP! 内部キューが満杯 STOP!
  19. Web Streams APIの基本と実践、TypeScriptでの活用法 – TSKaigi 2025 #TSKaigi #TSKaigi2025 #tskaigi_leverages 内部キューと背圧

    • 背圧は内部キューの状態から、キューイング戦略に基づいて通知される • 現在は2種類のキューイング戦略が利用可能 キューイング戦略と最高水準点(highWaterMark) 24 キューに格納されたチャンク数で判定 キューに格納されたバイト数で判定 chunk chunk chunk chunk chunk chunk chunk chunk 3 最高水準点 (highWaterMark) 10KB CountQueuingStrategy ByteLengthQueuingStrategy
  20. Web Streams APIの基本と実践、TypeScriptでの活用法 – TSKaigi 2025 #TSKaigi #TSKaigi2025 #tskaigi_leverages Node.jsのStreamとの違いと互換性

    • Node.jsの組み込みモジュール • こちらのほうが先発 • 「Streamを制するものはNode.jsを制す」と言われていたらしい • EventEmitterを継承していて、イベント駆動的 • highWaterMarkの考え方はNode StreamからWeb Streams APIに影響してそう • 3(+2)種類のストリームオブジェクト Node.js Streamについて 26 Readable 読み込み Duplex 双方向 (読み込み+書き込み) Transform 変換 Writable 書き込み PassThrou gh パススルー (何もしない)
  21. Web Streams APIの基本と実践、TypeScriptでの活用法 – TSKaigi 2025 #TSKaigi #TSKaigi2025 #tskaigi_leverages Node.jsのStreamとの違いと互換性

    • Node.jsでもv21でWeb Streams APIがStableになった • Node Stream Web Streams API は toWeb() / fromWeb() メソッドで相互に変換可能 • v17で追加されてから長らくExperimentalだったが、v24でとうとうStableになった Node SteamとWeb Streams APIとの互換性 27
  22. Web Streams APIの基本と実践、TypeScriptでの活用法 – TSKaigi 2025 #TSKaigi #TSKaigi2025 #tskaigi_leverages Node.jsのStreamとの違いと互換性

    • Web標準であること • クロスブラウザに利用できる • Fetch APIを始めとして、他の標準仕様にもWeb Streams APIベースのAPIが増えていっている • WinterTC Minimum Common APIにも含まれている • Node.js含めてサーバーサイドJSでも利用できる • インターフェースの改善 • EventEmitter/Callbackな書き方からPromise/async/awaitな書き方に • 利用者に公開されているメソッド・プロパティがかなり減っているため学習コストが減った • (逆に細かい制御がしづらくなったとも) • 型情報 (@types/node) • Node Stream: chunkの型がany • Web Streams API: chunkの型がGenericsで指定できる Node Streamと比べて良くなったと思うところ (1) 28
  23. Web Streams APIの基本と実践、TypeScriptでの活用法 – TSKaigi 2025 #TSKaigi #TSKaigi2025 #tskaigi_leverages Node.jsのStreamとの違いと互換性

    • エラーハンドリング • Node Streamでは、上流のストリームがエラー終了しても下流のストリームが閉じない • errorイベントのイベントリスナーで明示的に閉じる必要がある • Web Streams APIでは、勝手に閉じる • pipeThrough()/pipeTo()のpreventAbortオプションで制御可能 Node Streamと比べて良くなったと思うところ (2) 29
  24. Web Streams APIの基本と実践、TypeScriptでの活用法 – TSKaigi 2025 #TSKaigi #TSKaigi2025 #tskaigi_leverages Node.jsのStreamとの違いと互換性

    • 変換ストリームの結合が比較的やりやすくなった • Node Stream: Duplexでラップするが、実装が複雑になる • イベントリスナーやメソッドの繋ぎ込みがかなり面倒 • stream.compose()を利用すると簡単に結合できるが、まだExperimental • Web Streams API: TransformStreamでラップしてパイプ接続したらOK Node Streamと比べて良くなったと思うところ (3) 30
  25. Web Streams APIの基本と実践、TypeScriptでの活用法 – TSKaigi 2025 #TSKaigi #TSKaigi2025 #tskaigi_leverages Node.jsのStreamとの違いと互換性

    • これから書くコードは最初からWeb Streams APIで良い • Web標準かつPromiseベースの書き方ができる • Node Streamとも toWeb() / fromWeb() で相互変換できる • 昔からあるnpmパッケージはNode Streamを使っている • 当面は toWeb() / fromWeb() メソッドで変換しながら併用することになる Node SteamとWeb Streams API、どっちを使えばいい? 31
  26. Web Streams APIの基本と実践、TypeScriptでの活用法 – TSKaigi 2025 #TSKaigi #TSKaigi2025 #tskaigi_leverages Promiseベースの非同期処理との相互運用性

    • pipeTo()の返り値がPromise • パイプチェーンでのデータ処理が終わるまでawaitで待つことができる • チャンクを1つずつ操作したい場合 • ReadableStream.getReader()やWritableStream.getWriter()でreader/writerを取得 • await reader.read() / await writer.write() でチャンクを1つずつ読み込み/書き込みできる ストリームをasync/awaitの中で使う 33
  27. Web Streams APIの基本と実践、TypeScriptでの活用法 – TSKaigi 2025 #TSKaigi #TSKaigi2025 #tskaigi_leverages Promiseベースの非同期処理との相互運用性

    • ReadableStreamは非同期反復可能([Symbol.asyncIterator]()を実装している)(Safari除く) • for await ... ofで反復処理できる • Array.fromAsync()でデータを全て読み出して配列に格納できる ストリームを反復処理する 34
  28. Web Streams APIの基本と実践、TypeScriptでの活用法 – TSKaigi 2025 #TSKaigi #TSKaigi2025 #tskaigi_leverages Promiseベースの非同期処理との相互運用性

    • ReadableStream.from() • 反復可能オブジェクト or 非同期反復可能オブジェクトからReadableStreamを作成できる • ただしまだFireFox, Deno, Node.jsでしか利用できない イテラブルからストリームを作成する 35 https://developer.mozilla.org/ja/docs/Web/API/ReadableStream/from_static
  29. Web Streams APIの基本と実践、TypeScriptでの活用法 – TSKaigi 2025 #TSKaigi #TSKaigi2025 #tskaigi_leverages おわりに

    • Streams APIには3つのストリームオブジェクトがある • ReadableStream, TransformStream, WritableStream • ストリームオブジェクトをパイプ接続してデータを逐次処理できる • 背圧を制御することで処理速度を制御し、メモリ使用量を適切に抑えることができる • Node Streamとは相互に変換可能 • 新規に書くコードはWeb Streams APIで良い • async/awaitな処理とも組み合わせやすい まとめ 36
  30. Web Streams APIの基本と実践、TypeScriptでの活用法 – TSKaigi 2025 #TSKaigi #TSKaigi2025 #tskaigi_leverages おわりに

    • kintoneを開発する仲間を探しています!! • 採用フォームからでも、XのDMでもどうぞ! • https://cybozu.co.jp/recruit/ We are hiring!! 37 Webエンジニア (kintone) エンジニアリング マネージャー (kintone) Webエンジニア (kintone/生成AI) フロントエンド エキスパート
  31. Web Streams APIの基本と実践、TypeScriptでの活用法 – TSKaigi 2025 #TSKaigi #TSKaigi2025 #tskaigi_leverages 付録:ストリームオブジェクトの補足

    • チャンクの読み込みはReader経由で行う • ソースにはPull型とPush型がある • Pull型:データはストリームから明示的に読み込む • 例:ファイルアクセスなど • https://streams.spec.whatwg.org/#example-rs-pull • Push型:データは勝手にソースから送信される、イベントリスナなどでストリームにenqueueする • 例:動画ストリーム、TCP/WebSocketsなど • https://streams.spec.whatwg.org/#example-rs-push-backpressure ReadableStream 40 Source chunk enqueue ReadableStream chunk chunk chunk chunk Reader 内部キュー raw data
  32. Web Streams APIの基本と実践、TypeScriptでの活用法 – TSKaigi 2025 #TSKaigi #TSKaigi2025 #tskaigi_leverages 付録:ストリームオブジェクトの補足

    • チャンクの書き込みはWriter経由で行う WritableStream 41 Sink chunk enqueue WritableStream chunk chunk chunk chunk Writer 内部キュー data
  33. Web Streams APIの基本と実践、TypeScriptでの活用法 – TSKaigi 2025 #TSKaigi #TSKaigi2025 #tskaigi_leverages 付録:ストリームオブジェクトの補足

    • 内部的にはWritableStream + ReadableStream • 入力側: TransformStream.writable (WritableStream) • 出力側: TransformStream.readable (ReadableStream) • 内部キューも入力側・出力側それぞれにある TransformStream (変換ストリーム) 42 readable chunk chunk writable chunk TransformStream transform() chunk データの入力側 データの出力側 データの変換
  34. Web Streams APIの基本と実践、TypeScriptでの活用法 – TSKaigi 2025 #TSKaigi #TSKaigi2025 #tskaigi_leverages 付録

    • tee()で分配された2つのReadableStreamは消費速度が速い方の速度で背圧制御される • 時間経過と共に速度が遅い方の内部キューにデータが滞留してしまう • 背圧制御を変更するオプションが提案されている(whatwg/streams#1235) • Cloudflare Workersでは tee の背圧制御を独自に修正している(cloudflare/workerd#85) • https://community.cloudflare.com/t/467416 ストリームの分配 (tee) と背圧制御の問題 43 Readable Stream Readable Stream 両方に同時にchunkを送信 後続の 処理が速い 後続の 処理が遅い ここにチャンクが溜まってしまう
  35. Web Streams APIの基本と実践、TypeScriptでの活用法 – TSKaigi 2025 #TSKaigi #TSKaigi2025 #tskaigi_leverages 付録

    • 検証時の通信方式 • APIレスポンスはJSONL形式 (Content-Type: application/jsonl; charset=utf-8) • HTTP/1.1 • Transfer-Encoding: chunked (レスポンスはチャンク単位で送られてくる) • => 背圧はunderlying sourceへのネットワークリクエストにも反映される • 内部キューが溜まってくるとウィンドウサイズが小さくなり、受信可能データを調整する • それでも処理しきれずに受信を止める場合は、zero windowパケットが送信される ネットワーク通信に背圧は反映されているのか? 44 chunk Server chunk chunk Response STOP! STOP! (zero window) https://speakerdeck.com/tasshi/web-streams-api-and-tcp-flow-control