小川は絶え間なく流れ続け、コンピュータサイエンスにおける連続的なデータ伝送に似ています。ウェブストリームの年からのものです。身近な例を考えてみましょう:GPTのストリーミング出力です。LLMの出力には時間がかかり、LLMがストリーミング方式で出力するため、生成されると同時にデータを送信し、結果をより早く見ることができます。フロントエンド開発者にとって、ストリームAPIがブラウザで利用可能になる前は、Ajaxリクエストをストリーミング方式で処理することができませんでした(EventSourceを回避策として使用する場合を除く)。これにより、大量のデータリクエストの場合、すべてのデータが返されるのを待ってからコールバック関数で処理する必要があり、ユーザーエクスペリエンスが大幅に低下していました。幸いなことに、2016年頃にブラウザのストリームAPIがついに更新されました。fetchのストリーミングリターンに加えて、ブラウザには他にも多くのストリーム関連のインターフェースがあります。ストリームの基本構造ここでは、最も基本的な2種類のストリームである読み取り可能なストリームと書き込み可能なストリームについて説明します。他のストリーム関連のインターフェースはこれらを基礎としているため、これら2つを理解することで他のインターフェースも理解しやすくなります。オプション);基底ソースは、controllerパラメータを受け取ります。これはReadableStreamDefaultControllerです。ReadableStreamDefaultController.desiredSize(キュー戦略については後で説明します)ReadableStreamDefaultController.close()ReadableStreamDefaultController.enqueue()ReadableStreamDefaultController.error()controller.enqueueを使用してデータを提供できます。enqueueが呼び出されると、データはリーダーが消費するためのキューに入ります。ストリームには境界がありませんが、そのような無限の概念は人間が実装するシステムには存在し得ません。ブラウザのストリームAPIでは、ストリームの伝送はチャンクに分割されます。上記のenqueueメソッドは、chunkをパラメータとして受け取ります。チャンクのタイプは一般的に文字列またはUint8Arrayですが、通常の読み取り可能なストリームはチャンクのタイプを制限しないため、オブジェクトも渡すことができます。(これは読み取り可能なバイトストリームについては議論しません)stream.getReader()を使うと、対応するストリームのデフォルトリーダーを取得できます。e),);リーダブルストリームが一度リーダーに接続されると、そのストリームはロックされます。メソッドを使ってストリームを分割することができます。パラメータを受け取ります。このコントローラーは、読み取りストリームコントローラーに比べて操作が少なく、主にストリームの終了に関与します。エラーを発生させ、ストリームを終了します次に、ライターについて説明します。使用することで、対応するストリームのデフォルトライターを取得できます。読み取り可能なストリームと書き込み可能なストリームの他に、データ処理に使用される別のタイプのストリームがあり、これを変換ストリームと呼びます。使って変換ストリームを作成することができますが、実際には読み取りと書き込みの両方の機能を持つストリームはすべて変換ストリームと見なされます。ストリームを使用する場合、「パイプ操作」が一般的に使用されます。ストリームを一緒にリンクすることができます。読み取り可能なストリームを書き込み可能なストリームに接続します。ストリームに接続します。説明キュー戦略とは、前述のように、読み取り可能および書き込み可能なストリームを作成する際に提供する必要がある2番目のパラメータを指します。この戦略は、highWaterMarkを使用してストリームのキューのサイズを指定し、ストリーム間のバッファとして機能します。このサイズは絶対的な制限ではなく、あくまでガイドラインであることに注意してください。キュー戦略が必要なのでしょうか?それは、読み取り可能および書き込み可能なストリームのデータ処理速度が一貫していないためです。速度に変動がある場合、バッファとしてキューを持つことで処理効率を向上させることができます。広く言えば、2つのタイプがあります。...キューが容量に達した場合、メモリのオーバーフローを防ぐために上位レイヤーに減速を促す信号を送る必要があります。これは、洗面器に水を注ぐ状況に似ています。洗面器が満杯になると、それ以上水を追加することはできません。この状況はバックプレッシャーと呼ばれ、desiredSizeによって示されます。内部キュー内の量を引いた値として計算されます。プロパティについて再確認しましょう:ReadableStreamDefaultController.desiredSizeWritableStreamDefaultWriter.desiredSize一方はReadableStreamのコントローラーにあり、もう一方はWritableStreamのライターにあります。一方がコントローラーに関連し、もう一方がライターに関連しているのか疑問に思うかもしれません。以下の図がその理由を明確にします!これらの2つの図は、ReadableStreamとWritableStreamおよびそれらのキューとの関係を要約しており、キューがどこにあるかを明確に示しています。読み取り可能なストリーム:書き込み可能なストリーム:内部キューに空きがある場合、リーダブルストリームのpullメソッドが自動的に呼び出され、高水位マークに達するまで続きます。startメソッドでデータの生成方法を定義する場合、desiredSizeが正でないときにデータの生成を停止するようにしてください。書き込み側について:書き込みストリームとして、通常はdesiredSizeの値を直接気にすることはありません。WritableStreamDefaultWriter.readyを覚えていますか?readyがtrueのとき、desiredSizeは正です。writer.readyを使用し、キューを適切に管理します。簡単に言えば、基礎となるソースと基礎となるシンクは常にストリーム全体の両端にあり、キューはその間に位置しています。キュー蓄積デモ上記の概念を視覚化するには、デモをご覧ください。rQueuingStrategy,)このReadableStreamは、start時にデータを生成することはなく、ボタンクリックのコールバックを登録するだけです。ボタンがクリックされると、データがenqueueされます。内部キューに空きがあるときは、pull関数が自動的に呼び出されます。キューに空きがある限り、データは継続的にenqueueされます。wQueuingStrategy,)writableStreamでは、5秒ごとに1つのアイテムを消費する非常に遅い速度をシミュレートします。これにより、キューが蓄積され、キューの動作を簡単に観察できるようになります。}}コードが実行されると、最初に基礎スロットがデータを処理するのに5秒かかります。両側の状態を確認することで、書き込みストリームがどれだけ書き込んだか、読み取りストリームがどれだけ読んだかを把握できます。両者のカウントの差がキューの容量を示します。コンソールでは、desiredSizeが常に0のままであることに気付くでしょう。enqueueボタンをクリックするとデータを追加でき、キューにどんどん追加されていきます。これは、highWaterMarkが単なるガイドラインであり、厳密な制限ではないことを意味します。キューがhighWaterMarkを超えてもデータ操作は可能です。これはメモリを消費するため、コストがかかります。そのため、ストリームを設計する際には、desiredSizeが0のときにさらにデータを生成しないように注意することが重要です。バックエンドからフロントエンドに暗号化された大容量のtxtファイルを転送します。フロントエンドはファイルを復号して表示します。ストリーミングを使用する場合と、ファイル全体を受信してから処理する場合の速度の違いを比較します。file.pipe(encryptStream).pipe(res);});バックエンドサービスから始めましょう。MBのファイルを読み込み、暗号化してフロントエンドに返します。暗号化プロセスは非常にシンプルです。0から255までの値をランダムにシャッフルするマップを作成し、各バイトを暗号化された値にマッピングします。堅牢な暗号化システムを実現するためには、実際の暗号化アルゴリズムを使用し、WebAssembly(wasm)の利用やキーの定期的な更新などの追加対策を検討する必要があります。この時点で、ブラウザの開発者ツールを開くと、リクエスト内容が乱れたテキストの塊であることがわかります。これは、マッピングされた内容がUTF-8で全くデコードできないためです。フロントエンドはこのインターフェースを2つの方法で処理します。1つ目はストリーミング処理後にデコードする方法、2つ目はすべてのデータが到着してから一度に処理する方法です。request.`);};テストの結果、これら二つの処理方法の間でユーザー体験に大きな違いがあることが判明しました。ストリーム処理ではコンテンツが即座に表示されるのに対し、従来の方法ではコンテンツが表示されるまでに2秒の待ち時間が必要です。一万行のログを送信する必要がある場合、すべてのデータが到着するのを待つのは現実的ではなく、すべてのデータを一度にレンダリングすることはパフォーマンスの問題を引き起こす可能性があります。したがって、このシナリオではストリーミングを使用することが適切な解決策です。}}ログ出力を処理する際には、fetchを通じて取得されるストリームが改行文字で自動的に分割されないことに注意する必要があります。そのため、「行」を定義するための独自のロジックを実装する必要があります。これは「境界のないストリーム」の良い例です。コンソール出力のchunkを確認すると、データの境界が\nで定義されていないことがわかります。したがって、データのグループ化を正しく処理するためにLineSplitStreamを実装する必要があります。HTTPの上に他のプロトコルを構築する場合、このステップは重要です。データチャンクには明確な境界が必要であり、正確に処理される必要があります。