多くの場合、別のページに移動したりフォームの送信など、ユーザーが何をしたりするかを記録するために、いくつかのデータを含むHTTPリクエストを送信する必要があります。この例を考えてみましょう。リンクをクリックするときに外部サービスに情報を送信します。
<a href="https://www.php.cn/link/3cbfb2330b21840b385a45c958602663">ページに移動します</a> document.getElementById( 'link')。addeventlistener( 'click'、(e)=> { fetch( "/log"、{ 方法:「投稿」、 ヘッダー:{ 「コンテンツタイプ」:「アプリケーション/JSON」 }、 ボディ:json.stringify({ いくつか:「データ」 }) }); });
ここには特に複雑なことはありません。リンクは通常どおり機能します( e.preventDefault()
を使用しませんでした)が、その動作が発生する前に、POSTリクエストがトリガーされます。応答を待つ必要はありません。訪問しているサービスに送信したいだけです。
一見すると、リクエストのスケジューリングが同期されていると思われるかもしれません。その後、ページからナビゲートし続け、他のサーバーがリクエストの処理を正常に処理します。しかし、これは必ずしもそうではないことがわかります。
ブラウザ内のページを終了するために特定のイベントが発生した場合、処理されるHTTP要求が成功するという保証はありません(ページ「終了」および他の状態のライフサイクルの詳細)。これらの要求の信頼性は、ネットワーク接続、アプリケーションのパフォーマンス、さらには外部サービス自体の構成など、多くの要因に依存する場合があります。
したがって、これらの瞬間にデータを送信することは信頼性からはほど遠い場合があります。これらのログに頼ってデータに敏感なビジネス上の意思決定を行うと、潜在的に重大な問題が発生する可能性があります。
この信頼性を示すために、上記のコードを使用してページを使用して小さなExpressアプリケーションを設定します。リンクがクリックされると、ブラウザは/other
にナビゲートされますが、これが発生する前に、POSTリクエストが発行されます。
すべてが起こっている間に、ブラウザの「ネットワーク」タブを開き、「スロー3G」接続速度を使用しています。ページが読み込まれた後、私はログをクリアし、すべてが静かに見える:
しかし、リンクをクリックすると、事態はうまくいきます。ナビゲーションが発生すると、リクエストがキャンセルされます。
これにより、外部サービスが実際にリクエストを処理できることを確認することはほとんどできません。この動作を検証するために、これはwindow.location
を使用してプログラムでナビゲートするときにも起こります。
document.getElementById( 'link')。addeventlistener( 'click'、(e)=> { E.PreventDefault(); //リクエストはキューに登録されていますが、ナビゲーションが発生した直後にキャンセルされました。 fetch( "/log"、{ 方法:「投稿」、 ヘッダー:{ 「コンテンツタイプ」:「アプリケーション/JSON」 }、 ボディ:json.stringify({ いくつか:「データ」 })、 }); window.location = e.target.href; });
これらの未完成のリクエストは、ナビゲーションがどのように、いつ発生するか、いつアクティブページが終了するかに関係なく、放棄される場合があります。
問題の根本的な原因は、デフォルトでは、XHR要求( fetch
またはXMLHttpRequest
経由)が非同期および非ブロッキングであることです。要求がキューになったら、リクエストの実際の作業がバックグラウンドでブラウザレベルのAPIに渡されます。
これはパフォーマンスの点で優れています - リクエストがメインスレッドを取り上げることを望んでいません。しかし、これはまた、ページが「終了」状態に入ると、放棄されるリスクがあり、背景作業を完了できるという保証がないことを意味します。この特定のライフサイクル状態のGoogleの要約は次のとおりです。
ページがアンインストールを開始し、ブラウザによってメモリからクリアされると、終了した状態になります。この状態では、新しいタスクを開始できず、進行中のタスクが長すぎると終了する場合があります。
要するに、ブラウザの設計の仮定は、ページが閉じられているときに、キューする背景プロセスの処理を継続する必要はないということです。
この問題を回避する最も明白な方法は、リクエストが応答を返すまで、ユーザーアクションを可能な限り遅らせることです。過去には、これはXMLHttpRequest
でサポートされている同期フラグを使用して誤って行われました。しかし、それを使用するとメインスレッドが完全にブロックされ、多くのパフォーマンスの問題が発生します - 私は過去にこれについていくつか書いたので、このアイデアを考慮すべきではありません。実際、プラットフォームを終了しようとしています(Chrome V80はすでに削除されています)。
代わりに、このアプローチを採用する場合は、返された応答を解決する約束を待つ方が良いでしょう。戻ったら、動作を安全に実行できます。以前のコードスニペットを使用して、これは次のようになるかもしれません。
document.getElementById( 'link')。addeventlistener( 'click'、async(e)=> { E.PreventDefault(); //応答が返されるのを待ちます... fetch( "/log"、{ 方法:「投稿」、 ヘッダー:{ 「コンテンツタイプ」:「アプリケーション/JSON」 }、 ボディ:json.stringify({ いくつか:「データ」 })、 }); //…そして、去るためにナビゲートします。 window.location = e.target.href; });
これは仕事をすることができますが、いくつかの自明な欠点があります。
まず、必要な動作の発生を遅らせることにより、ユーザーエクスペリエンスに影響します。分析データを収集することは、ビジネス(および将来のユーザー)にとって確かに有益ですが、それは現在のユーザーがこれらの利点を達成するためにお金を払わせるため、理想とはほど遠いものです。言うまでもなく、外部依存関係として、サービス自体のレイテンシまたはその他のパフォーマンスの問題は、ユーザーに反映されます。分析サービスからのタイムアウトにより、顧客が価値の高い操作を完了できなくなった場合、誰もが失われます。
第二に、このアプローチは、最初の音ほど信頼性がありません。一部の終了動作はプログラムで遅らせることはできないためです。たとえば、 e.preventDefault()
ユーザーが[ブラウザ]タブを閉じるように遅延するのに役に立たない。したがって、せいぜい、特定のユーザーアクションからのデータの収集のみをカバーしますが、完全に信頼するには十分ではありません。
ありがたいことに、ほとんどのブラウザには、ユーザーエクスペリエンスを損なうことなく未完成のHTTP要求を保持するための組み込みオプションがあります。
fetch()
を使用するときにkeepalive
フラグがtrue
に設定されている場合、リクエストを開始したページが終了した場合でも、対応する要求は開いたままです。最初の例を使用して、これにより実装が次のようになります。
<a href="https://www.php.cn/link/3cbfb2330b21840b385a45c958602663">ページに移動します</a> document.getElementById( 'link')。addeventlistener( 'click'、(e)=> { fetch( "/log"、{ 方法:「投稿」、 ヘッダー:{ 「コンテンツタイプ」:「アプリケーション/JSON」 }、 ボディ:json.stringify({ いくつか:「データ」 })、 Keepalive:本当です }); });
リンクとページナビゲーションをクリックすると、要求のキャンセルは発生しません。
代わりに、アクティブなページが応答が受信されるのを待つことができないという理由だけで、(未知の)状態を取得します。
このようなシングルラインコードは、特に一般的なブラウザAPIの一部である場合、簡単に修正できます。ただし、よりシンプルなインターフェイスを使用して、より集中化されたオプションを探している場合は、ほぼ同じブラウザーサポートでそれを行う別の方法があります。
Navigator.sendBeacon()
関数は、一方向リクエスト(ビーコン)を送信するために特別に使用されます。基本的な実装は次のとおりです。これは、Stringified JSONと「テキスト/プレーン」 Content-Type
投稿を送信します。
navigator.sendbeacon( '/log'、json.stringify({ いくつか:「データ」 }));
ただし、このAPIでは、カスタムヘッダーを送信することはできません。したがって、データを「アプリケーション/JSON」として送信するには、小さな調整を行い、BLOBを使用する必要があります。
<a href="https://www.php.cn/link/3cbfb2330b21840b385a45c958602663">ページに移動します</a> document.getElementById( 'link')。addeventlistener( 'click'、(e)=> { const blob = new blob([json.stringify({some: "data"})]、{type: 'application/json; charset = utf-8'}); navigator.sendbeacon( '/log'、blob); });
最終的に、同じ結果が得られました。ページがナビゲートされた後でも、リクエストは完了しました。しかし、発生していることがいくつかあります。これは、 fetch()
よりも優れている可能性があります。ビーコンは低優先度で送信されます。
デモンストレーションについては、 fetch()
とsendBeacon()
の両方をkeepalive
で使用する場合、ネットワークタブに表示されるものを次に示します。
デフォルトでは、 fetch()
は「高い」優先度を取得し、ビーコン(上記の「ping」タイプとは記載)は「最も低い」優先度を持っています。これは、ページの機能にとって重要ではないリクエストにとって良いことです。ビーコン仕様から直接:
この仕様は、[…]他の時間批判的な操作とのリソース競争を最小限に抑えながら、そのような要求がまだ処理され、目的地に配信されることを保証するインターフェイスを定義します。
言い換えれば、 sendBeacon()
その要求があなたのアプリケーションとユーザーエクスペリエンスにとって本当に重要な要求を妨げないことを保証します。
ますます多くのブラウザがping
属性をサポートすることに言及する価値があります。リンクに添付されると、小さなPOSTリクエストが発行されます。
<a href="https://www.php.cn/link/fef56cae0dfbabedeadb64bf881ab64f" ping="http://localhost:3000/log"> 他のページに移動します </a>
これらのリクエストヘッダーには、リンクがクリックされるページ(ping-from)と、リンクのhref値(ping-to)が含まれます。
<code>headers: { 'ping-from': 'http://localhost:3000/', 'ping-to': 'https://www.php.cn/link/fef56cae0dfbabedeadb64bf881ab64f' 'content-type': 'text/ping' // ...其他标头},</code>
技術的にはビーコンの送信に似ていますが、いくつかの顕著な制限があります。
全体として、 ping
簡単なリクエストのみを送信し、カスタムJavaScriptを書きたくない場合に最適なツールです。ただし、より多くのコンテンツを送信する必要がある場合は、最良の選択肢ではない場合があります。
keepalive
を使用してfetch()
またはsendBeacon()
を使用して最後の秒リクエストを送信する場合、確かにトレードオフがあります。さまざまな状況に最適なアプローチを伝えるために、考慮すべきことがいくつかあります。
ページが終了したときにブラウザがインプロセスのリクエストを処理する方法を深く掘り下げることを選択した理由があります。少し前に、フォームを提出したときにすぐにリクエストを発射し始めた後、私たちのチームは、特定の種類の分析ログの頻度が突然変化したことを発見しました。この変化は突然で重要です。歴史上私たちが見たものから約30%減少しています。
この問題の原因と、問題を再現しないように利用可能なツールを掘り下げると、その日は節約されました。ですから、もしあれば、これらの課題のニュアンスが、私たちが遭遇する痛みの一部を避けるのに役立つことを理解したいと思っています。ハッピーレコード!
以上がユーザーがページを残すと、HTTPリクエストを確実に送信しますの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。