paint-brush
JavaScript 拡張フォームで重複するフェッチ リクエストをキャンセルする方法に関するガイド@austingil
2,570 測定値
2,570 測定値

JavaScript 拡張フォームで重複するフェッチ リクエストをキャンセルする方法に関するガイド

Austin Gil8m2023/02/10
Read on Terminal Reader

長すぎる; 読むには

誤って重複リクエスト/競合状態のバグを導入した可能性が十分にあります。今日は、この問題とそれを回避するための推奨事項について説明します。ここでの重要な問題は、`event.preventDefault()` です。このメソッドは、ブラウザが新しいページをロードしてフォームを送信するデフォルトの動作を実行するのを防ぎます。
featured image - JavaScript 拡張フォームで重複するフェッチ リクエストをキャンセルする方法に関するガイド
Austin Gil HackerNoon profile picture

フォームの送信を強化するために JavaScript fetch API を使用したことがある場合は、誤って重複リクエスト/競合状態のバグを導入した可能性があります。今日は、この問題とそれを回避するための推奨事項について説明します。


(よろしかったら最後に動画を)


非常に基本的なことを考えてみましょうHTMLフォーム単一の入力と送信ボタンを使用します。


 <form method="post"> <label for="name">Name</label> <input id="name" name="name" /> <button>Submit</button> </form> 


送信ボタンを押すと、ブラウザはページ全体を更新します。


送信ボタンをクリックした後、ブラウザーがどのようにリロードされるかに注意してください。


ページの更新は、常にユーザーに提供したいエクスペリエンスとは限らないため、一般的な代替手段は使用することですJavaScriptフォームの「submit」イベントにイベント リスナーを追加し、デフォルトの動作を回避して、 fetch API を使用してフォーム データを送信します。


単純化したアプローチは、以下の例のようになります。


ページ (またはコンポーネント) がマウントされたら、フォーム DOM ノードを取得し、フォームを使用してfetch要求を構築するイベント リスナーを追加します。アクション方法、 とデータ、そしてハンドラーの最後で、イベントのpreventDefault()メソッドを呼び出します。


 const form = document.querySelector('form'); form.addEventListener('submit', handleSubmit); function handleSubmit(event) { const form = event.currentTarget; fetch(form.action, { method: form.method, body: new FormData(form) }); event.preventDefault(); }


さて、JavaScript の有力者が GET と POST、およびリクエストの本文とコンテンツ タイプ他に何があっても、私に言わせてください、私は知っています。 fetchリクエストは意図的にシンプルにしています。これは主な焦点ではないからです。


ここでの重要な問題はevent.preventDefault()です。このメソッドは、ブラウザが新しいページをロードしてフォームを送信するデフォルトの動作を実行するのを防ぎます。


ここで、画面を見て [送信] をクリックすると、ページがリロードされていないことがわかりますが、[ネットワーク] タブに HTTP 要求が表示されています。


ブラウザはページ全体のリロードを行わないことに注意してください。


残念ながら、JavaScript を使用してデフォルトの動作を防止することで、デフォルトのブラウザ動作にはないバグを実際に導入してしまいました。


プレーンを使用する場合HTML送信ボタンをすばやく何度も押すと、最新のものを除くすべてのネットワーク リクエストが赤くなることに気付くでしょう。これは、それらがキャンセルされたことを示し、最新のリクエストのみが受け入れられます。


これを JavaScript の例と比較すると、すべてのリクエストが送信され、キャンセルされることなくすべてが完了していることがわかります。


各リクエストには異なる時間がかかる場合がありますが、開始された順序とは異なる順序で解決される可能性があるため、これは問題になる可能性があります。つまり、これらのリクエストの解決に機能を追加すると、予期しない動作が発生する可能性があります。


例として、リクエストごとにインクリメントする変数を作成できます (「 totalRequestCount 」)。 handleSubmit関数を実行するたびに、合計カウントをインクリメントするだけでなく、現在の数を取得して現在のリクエストを追跡できます (「 thisRequestNumber 」)。


fetchリクエストが解決されると、対応する番号をコンソールに記録できます。


 const form = document.querySelector('form'); form.addEventListener('submit', handleSubmit); let totalRequestCount = 0 function handleSubmit(event) { totalRequestCount += 1 const thisRequestNumber = totalRequestCount const form = event.currentTarget; fetch(form.action, { method: form.method, body: new FormData(form) }).then(() => { console.log(thisRequestNumber) }) event.preventDefault(); }


ここで、送信ボタンを何度も押すと、2、3、1、4、5 のように異なる数字がコンソールに間違った順序で出力されることがあります。これはネットワークの速度によって異なりますが、私たち全員が同意できると思います。これは理想的ではないということです。


ユーザーがいくつかのfetchリクエストを連続してトリガーし、完了すると、アプリケーションが変更内容でページを更新するシナリオを考えてみましょう。リクエストが順不同で解決されるため、ユーザーは最終的に不正確な情報を表示する可能性があります。


これは非 JavaScript の世界では問題ではありません。ブラウザは以前のリクエストをキャンセルし、最新のリクエストが完了した後にページをロードして、最新バージョンをロードするからです。しかし、ページの更新は魅力的ではありません。


JavaScript 愛好家にとって良いニュースは、両方を使用できることです。セクシーなユーザー体験そして一貫したUI!


もう少し足を運ぶ必要があります。


fetch API ドキュメントを見ると、 AbortControllerfetchオプションのsignalプロパティを使用してフェッチを中止できることがわかります。次のようになります。


 const controller = new AbortController(); fetch(url, { signal: controller.signal });


AbortContollerのシグナルをfetchリクエストに提供することで、 AbortContollerabortメソッドがトリガーされたときにいつでもリクエストをキャンセルできます。


JavaScript コンソールでより明確な例を確認できます。 AbortControllerを作成し、 fetchリクエストを開始してから、すぐにabortメソッドを実行してみてください。


 const controller = new AbortController(); fetch('', { signal: controller.signal }); controller.abort()


すぐにコンソールに例外が表示されるはずです。 Chromium ブラウザーでは、「Uncaught (in promise) DOMException: The user aborted a request.」と表示されます。また、[ネットワーク] タブを調べると、ステータス テキストが「(キャンセル)」の失敗した要求が表示されます。


JavaScript コンソールを開いた状態で、ネットワークに対して開かれた Chrome 開発ツール。コンソールには、「const controller = new AbortController();fetch('', { signal: controller.signal });controller.abort()」というコードがあり、その後に「Uncaught (in promise) DOMException: Theユーザーがリクエストを中止しました。」ネットワークでは、ステータス テキストが「(キャンセル)」の「localhost」へのリクエストがあります。

これを念頭に置いて、フォームの送信ハンドラーにAbortControllerを追加できます。ロジックは次のようになります。


  • 最初に、以前のリクエストのAbortControllerを確認します。存在する場合は、中止します。


  • 次に、後続のリクエストで中止できる現在のリクエストのAbortControllerを作成します。


  • 最後に、リクエストが解決したら、対応するAbortControllerを削除します。


これを行うにはいくつかの方法がありますが、ここではWeakMapを使用して、送信された各<form> DOM ノードとそれぞれのAbortControllerの間の関係を保存します。フォームが送信されると、それに応じてWeakMap確認および更新できます。


 const pendingForms = new WeakMap(); function handleSubmit(event) { const form = event.currentTarget; const previousController = pendingForms.get(form); if (previousController) { previousController.abort(); } const controller = new AbortController(); pendingForms.set(form, controller); fetch(form.action, { method: form.method, body: new FormData(form), signal: controller.signal, }).then(() => { pendingForms.delete(form); }); event.preventDefault(); } const forms = document.querySelectorAll('form'); for (const form of forms) { form.addEventListener('submit', handleSubmit); }


重要なことは、アボート コントローラーを対応するフォームに関連付けることができることです。そのためには、フォームの DOM ノードをWeakMapのキーとして使用すると便利です。


これで、 fetch要求にAbortControllerのシグナルを追加し、以前のコントローラーを中止し、新しいコントローラーを追加し、完了時にそれらを削除できます。


うまくいけば、それはすべて理にかなっています。


ここで、そのフォームの送信ボタンを何度も押すと、最新の API リクエストを除くすべての API リクエストがキャンセルされることがわかります。


これは、その HTTP 応答に応答するすべての関数が、期待どおりに動作することを意味します。


ここで、上記と同じカウントおよびロギング ロジックを使用すると、送信ボタンを 7 回押すと、( AbortControllerによる) 6 つの例外と、コンソールに「7」のログが 1 つ表示されます。


再度送信して、リクエストが解決されるまで十分な時間を確保すると、コンソールに「8」が表示されます。また、送信ボタンを何度も押しても、例外と最終リクエスト数が正しい順序で表示され続けます。


リクエストが中止されたときにコンソールに DOMExceptions が表示されないようにするロジックを追加したい場合は、 fetchリクエストの後に.catch()ブロックを追加し、エラーの名前が「 AbortError 」と一致するかどうかを確認します。


 fetch(url, { signal: controller.signal, }).catch((error) => { // If the request was aborted, do nothing if (error.name === 'AbortError') return; // Otherwise, handle the error here or throw it back to the console throw error });

閉鎖

この投稿全体は JavaScript で強化されたフォームに焦点を当てていましたが、 fetchリクエストを作成するときはいつでもAbortControllerを含めることをお勧めします。まだAPIに組み込まれていないのは本当に残念です。しかし、うまくいけば、これはそれを含めるための良い方法を示しています.


また、このアプローチでは、ユーザーが送信ボタンを何度もスパム送信するのを防ぐことはできないことにも注意してください。ボタンは引き続きクリック可能であり、リクエストは引き続き発生します。これにより、レスポンスを処理するためのより一貫した方法が提供されます。


残念ながら、ユーザーが送信ボタンをスパムし場合、それらのリクエストは引き続きバックエンドに送られ、不要なリソースを大量に消費する可能性があります。


いくつかの単純な解決策は、送信ボタンを無効にしている可能性があります。デバウンス、または以前のリクエストが解決された後にのみ新しいリクエストを作成します。これらのオプションは、ユーザー エクスペリエンスを遅くすることに依存しており、クライアント側でしか機能しないため、好きではありません。


スクリプト化されたリクエストによる不正行為には対処しません。


サーバーへのリクエストが多すぎることによる悪用に対処するには、おそらくいくつかのレート制限.これはこの投稿の範囲を超えていますが、言及する価値はありました。


また、レート制限は、リクエストの重複、競合状態、一貫性のない UI 更新などの元の問題を解決しないことにも注意してください。理想的には、両方を使用して両端をカバーする必要があります。


とにかく、今日はここまでです。これと同じテーマを取り上げたビデオを見たい場合は、これを見てください。

読んでいただきありがとうございます。この記事が気に入ったらどうぞ共有する.それは私をサポートする最良の方法の 1 つです。あなたもすることができますニュースレターにサインアップするまたTwitterで私に従ってください新しい記事が公開されたときに知りたい場合。


最初に公開されたaustingil.com .