ホームページ >ウェブフロントエンド >jsチュートリアル >チャンクバスターズ: 小川を渡らないでください!

チャンクバスターズ: 小川を渡らないでください!

Linda Hamilton
Linda Hamiltonオリジナル
2024-12-02 05:00:10840ブラウズ

⚠️ 光過敏症がある場合は、おそらくこれをスキップした方がよいでしょう。
下の静止画像をご覧ください。ライトが高速で点滅し始めます!

Chunk-Busters: Don’t cross the Streams!

インターネットはどのように機能するのでしょうか?

タイトルを思い出してください…ここではストリームについて話しています。

プロトコル、パケット、順序付け、ACK、NACK について話すこともできます…しかし、ここではストリームについて話しています。おそらくご想像のとおり (私はそう信じています =D)、ストリームについては…バイナリまたは文字列のいずれかです。

はい、文字列は送信前に圧縮されます…しかし、フロントエンドとバックエンドの開発で私たちが通常気にするのは…文字列とバイナリです。

次の例では、JS ストリームを使用します。

Node には独自の従来の実装がありますが、前でも後ろでも同じコードのストリームを処理する方法があります。

他の言語でもストリームを扱う独自の方法がありますが、ご覧のとおり…ストリームを扱う実際のコード部分はそれほど複雑ではありません (複雑なことが起こっていないというわけではありません)。

例の問題

複数のソースからのデータを使用する必要があるフロントエンドがあります。

IP/ポートを介して各ソースに個別にアクセスできますが、使いやすさと制御を容易にするために、ソースを API ゲートウェイの背後に配置します。

レポ

リンクにあるリポジトリを確認してください。自分で実行する方法を学習して、実際に試してみてください。

https://github.com/Noriller/chunk-busters

ビデオ

フォローするビデオ バージョン:

https://youtu.be/QucaOfFI0fM

v0 - 単純な実装

ソースがあれば、フェッチし、待機し、レンダリングします。洗い流して繰り返します。

await fetch1();
handleResult(1);
await fetch2();
handleResult(2);
...
await fetch9();
handleResult(9);

実際にそんなことをする人はいないだろうと思っているかもしれません…

この例では、何かが間違っていることは明らかですが、これに陥るのはそれほど難しいことではありません。

明らかなことは、遅いということです。リクエストごとに起動して待機する必要があり、それが遅い場合は待機する必要があります。

v1 - 熱心なバージョン

各リクエストを個別に待ちたくないのはわかっているので、すべてを起動して完了するまで待ちます。

await Promise.all([
  fetch1(),
  fetch2(),
  ...
  fetch9(),
]);
handleAllResults(results);

これはおそらくあなたがやることなので、良いですよね?

つまり、1 つのリクエストが遅い場合を除いて…これは、たとえ他のリクエストがすべて完了していたとしても…そのリクエストが完了するまでまだ待たなければならないことを意味します。

v2 - よりスマートで熱心なバージョン

遅いリクエストがいくつかあるかもしれないことはわかっているので、それでもすべてを起動して待機しますが、リクエストが到着すると、可能な場合にはその結果に対してすでに何らかの処理を行っているため、最後のリクエストが到着した時点で、他のリクエストはすでに完了しています。

await fetch1();
handleResult(1);
await fetch2();
handleResult(2);
...
await fetch9();
handleResult(9);

これが最善の解決策であるはずですよね?

うーん…何か変ですか?

v3 - 私はあなたに嘘をついていました…これが v1 のあるべき姿です

v1 を覚えていますか?はい…これは次のようになります:

http/1 ではまったく同じエンドポイントに対して接続できる数に制限があることがわかりました。それだけではありません…これはブラウザーに依存しており、ブラウザーごとに制限が異なる可能性があります。

http/2 だけを使用して、もう終わりにしようと思うかもしれません…しかし、これが良い解決策だったとしても、フロントエンドで複数のエンドポイントを処理する必要があります。

これに対する良い解決策はありますか?

v4 - ストリームに参加してください!

v0 をもう一度見てみましょう。ただし、ストリームを使用します…

あなたは賢いので、警告によって少しネタバレされていたので、おそらくこれを期待していたのでしょう...でもそうです...以前に表示されていたのは、バックエンドが生成していたデータのすべてではありませんでした。

とにかく…フェッチしながらレンダリングします。

await Promise.all([
  fetch1(),
  fetch2(),
  ...
  fetch9(),
]);
handleAllResults(results);

代わりに、到来するストリームをタップすると、到来するデータの塊を使って何かを行うことができます。 (はい! Chat GPT などと同様です。)

v0 がこの問題を処理する最悪の方法であるとしても、ストリームを使用することで大幅に改善されます。合計待機時間が同じであっても、何かを表示することでユーザーをだますことができます。

v5 - v1 も同様ですが、ストリームが含まれています。

http/1 問題は依然として問題となっていますが、繰り返しになりますが、事態の推移はすでに確認できています。

そうだ…もうこれを止めることはできない…だから…

v6 - 1 つの API ですべてを制御!

それとも…できるかもしれません?

ご存知のとおり、フロントエンドは管理しなければならないことが多すぎました…それをバックエンドにオフロードできれば、すべてのソースを処理する 1 つのエンドポイントを持つことができます。

これにより、フロントエンドの複雑さと http/1 の問題が解決されます。

await Promise.all([
  fetch1().then(handleResult),
  fetch2().then(handleResult),
  ...
  fetch9().then(handleResult),
]);


// usually we do this:
await fetch(...).then((res) => {
  // this json call accumulate all the response
  // that later is returned for you to use
  return res.json()
})

v7 - そして最後に… 1 つの API、複数のソース、ストリーミング。

私たちは 1 つの API を呼び出します。この API は、すべてのソースを呼び出し、データをストリーミングし、それを処理し、フロントに渡します。フロントは、データを受信したときにレンダリングします。

これに使用されるコードは基本的に表と裏で同じです:

await fetchAll();
handleAllResults(results);

はい…以上です (最も基本的で簡単な例です)。

バッファに来る文字列を追加し、それを解析し、使用可能なチャンクがあるかどうかを確認し、それを使用し、それを忘れます。これは、少ない RAM で、一度に 1 チャンクずつ、TB のデータを受信/消費できることを意味します。

あなたが何を考えているかはわかります...そしてそれは愚かです...それは狂気でもあります...

うーん、Websocket が欲しいです!

いいえ、家には WebSocket があります!

自宅の WebSocket: 次は?

v8 - 機能しないのは愚かなだけです

あなたは賢いですね、ソースがまだデータを生成しているのであれば、いくつかの変数を更新できるかもしれないと考えたのですね…

この方法により、より多くのデータを取得したり、生成されたものから何かを変更したりするために使用されている 1 つの接続を維持できます。

はい…あなたならできると思います…そして私はあなたの主張に従ってその例を実行しました。 =D

それでも…これは愚かなアイデアです。実際の運用環境でどこで使用できるか、使用できるかどうかはわかりません。おそらく、MPA と Ajax の間の厄介な JS 段階にタイムスリップして、十分な対話性はあったものの、同じサーバーへの接続が十分ではなかった (ブラウザーによっては 2 つしか制限されていなかった!) かもしれません。

それ以外は、わかりません。もし持っているなら…知らせてください。

上の例では、中央のボード、特に「進行状況の境界線」に注目してください。更新され続けていることがわかります。 [ネットワーク] タブを開くと、GET 接続が最後まで閉じられていないことがわかります。また、その 1 つのまだ生きている接続の動作を変更する他のリクエストも複数表示されます。これらはすべてバニラの http/1 で行われます。

次は何でしょうか?

文字列とJSONの比較

この例は、私が作成できる最も基本的なものです。解析しやすいため、JSON の代わりに単純な文字列も使用しています。

JSON を使用するには、文字列を蓄積する必要があります (バックエンド応答を JSON.stringify する必要があるのには理由があります)。

次に、どこで分割するかを確認してからその値を解析するか、そのまま解析します。

最初の方法では、NDJSON について考えてみましょう。JSON 配列の代わりに、新しい行でオブジェクトを区切ると、「より簡単に」分割箇所を見つけて、それぞれを JSON.parse してオブジェクトを使用できます。

後者の場合は、次のように解析します。配列に入っていることがわかり、今度はオブジェクトです。OK 最初のキー、今度はキーの値、次のキー、それをスキップ、次のキー…などなど… 手動で行うのは簡単なことではありませんが、待機中にレンダリングにジャンプするようなものです。これはすべて…ただし、さらに小規模な場合を除きます。

エラー処理

人々はサンプルをホストすることを好みますが、これは自分で実行する必要があります…サンプルをどこかにホストしない理由が明確になったことを願っていますが、もう 1 つは、ここでエラーが発生することを期待していないということです。ネットワークエラーを何よりも優先してください…そうですね…

エラーは処理する必要がありますが、さらに複雑さが加わります。

それを使用する必要がありますか?

たぶん…次第と言えるでしょう…

ストリーミングが解決策となる場所もありますが、ほとんどの場合… await json で十分です (簡単であることは言うまでもありません)。

しかし、ストリームについて学ぶことで、フロントエンドであれバックエンドであれ、いくつかの問題を解決する方法が開かれます。

フロントエンドでは、これをいつでもユーザーを「騙す」ために使用できます。スピナーをどこにでも表示する代わりに、何かが現れたらそれを表示し、時間がかかってもさらに表示することができます。ユーザーの操作をブロックしない限り…スピナーを表示するだけよりも「遅い」ものを作成することもできます。実際には、他のものよりもはるかに速いように感じる .

バックエンドでは、フロントから、データベースから、またはその間にあるものから、データの各チャンクを受信したときに解析できるため、RAM を節約できます。必要に応じてデータを処理し、OOM (メモリ不足) エラーをスローするペイロード全体を待つことなくデータを送信します。 GB または TB のデータ…もちろん、そうしないのはなぜですか?

アウトロ

React は遅いですか?このフロントエンドのサンプル全体は React で作成されており、すべての「ライト」の点滅で起こっている「メイン」の処理のほかに、他にも多くの処理が行われています。

はい…十分に速くすると、サンプルが追いつかずフリーズし始めます。しかし、毎分何千ものレンダリングが簡単に行われるので、ほとんどのアプリケーションには十分だと思います。

そして、いつでもパフォーマンスを向上させることができます。「進行状況の境界線」については、レンダリングで一部を保存する必要がある場合に、遅延値を使用してスムーズにしています…「ライト」については、これと他のパフォーマンスの強化を行うことができます。とタイトルですが、これでは「ライト」の点滅が頻繁に停止するだけになり (良いデモにはなりません)、タイトルの「電気」の下線もそれほど楽しくありません。

この例では、これらすべての「改善」は理想的ではありませんが、通常のアプリケーションの場合は、多くのことを処理できるようにすることができます。さらに何か必要な場合は、別のソリューションを使用してください。

結論

自分の武器庫にストリームを追加しましょう…万能の解決策ではないかもしれませんが、いつかきっと役に立つでしょう。

それを使って何かをしようとしていて、助けが必要な場合は、そうですね…私に電話してください。 =P

以上がチャンクバスターズ: 小川を渡らないでください!の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。