フォームの送信を強化するために JavaScript API を使用したことがある場合は、誤って重複リクエスト/競合状態のバグを導入した可能性があります。今日は、この問題とそれを回避するための推奨事項について説明します。 fetch (よろしかったら最後に動画を) 非常に基本的なことを考えてみましょう 単一の入力と送信ボタンを使用します。 HTMLフォーム <form method="post"> <label for="name">Name</label> <input id="name" name="name" /> <button>Submit</button> </form> 送信ボタンを押すと、ブラウザはページ全体を更新します。 送信ボタンをクリックした後、ブラウザーがどのようにリロードされるかに注意してください。 ページの更新は、常にユーザーに提供したいエクスペリエンスとは限らないため、一般的な代替手段は使用することです フォームの「submit」イベントにイベント リスナーを追加し、デフォルトの動作を回避して、 API を使用してフォーム データを送信します。 JavaScript fetch 単純化したアプローチは、以下の例のようになります。 ページ (またはコンポーネント) がマウントされたら、フォーム 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! セクシーなユーザー体験 もう少し足を運ぶ必要があります。 API ドキュメントを見ると、 と オプションの プロパティを使用してフェッチを中止できることがわかります。次のようになります。 fetch AbortController fetch signal const controller = new AbortController(); fetch(url, { signal: controller.signal }); のシグナルを リクエストに提供することで、 の メソッドがトリガーされたときにいつでもリクエストをキャンセルできます。 AbortContoller fetch AbortContoller abort JavaScript コンソールでより明確な例を確認できます。 を作成し、 リクエストを開始してから、すぐに メソッドを実行してみてください。 AbortController fetch abort const controller = new AbortController(); fetch('', { signal: controller.signal }); controller.abort() すぐにコンソールに例外が表示されるはずです。 Chromium ブラウザーでは、「Uncaught (in promise) DOMException: The user aborted a request.」と表示されます。また、[ネットワーク] タブを調べると、ステータス テキストが「(キャンセル)」の失敗した要求が表示されます。 これを念頭に置いて、フォームの送信ハンドラーに を追加できます。ロジックは次のようになります。 AbortController 最初に、以前のリクエストの を確認します。存在する場合は、中止します。 AbortController 次に、後続のリクエストで中止できる現在のリクエストの を作成します。 AbortController 最後に、リクエストが解決したら、対応する を削除します。 AbortController これを行うにはいくつかの方法がありますが、ここでは を使用して、送信された各 DOM ノードとそれぞれの の間の関係を保存します。フォームが送信されると、それに応じて 確認および更新できます。 WeakMap <form> 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 回押すと、( による) 6 つの例外と、コンソールに「7」のログが 1 つ表示されます。 AbortController 再度送信して、リクエストが解決されるまで十分な時間を確保すると、コンソールに「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 で強化されたフォームに焦点を当てていましたが、 リクエストを作成するときはいつでも を含めることをお勧めします。まだAPIに組み込まれていないのは本当に残念です。しかし、うまくいけば、これはそれを含めるための良い方法を示しています. fetch AbortController また、このアプローチでは、ユーザーが送信ボタンを何度もスパム送信するのを防ぐことはできないことにも注意してください。ボタンは引き続きクリック可能であり、リクエストは引き続き発生します。これにより、レスポンスを処理するためのより一貫した方法が提供されます。 残念ながら、ユーザーが送信ボタンをスパムし 場合、それらのリクエストは引き続きバックエンドに送られ、不要なリソースを大量に消費する可能性があります。 た いくつかの単純な解決策は、送信ボタンを無効にしている可能性があります。 、または以前のリクエストが解決された後にのみ新しいリクエストを作成します。これらのオプションは、ユーザー エクスペリエンスを遅くすることに依存しており、クライアント側でしか機能しないため、好きではありません。 デバウンス スクリプト化されたリクエストによる不正行為には対処しません。 サーバーへのリクエストが多すぎることによる悪用に対処するには、おそらくいくつかの .これはこの投稿の範囲を超えていますが、言及する価値はありました。 レート制限 また、レート制限は、リクエストの重複、競合状態、一貫性のない UI 更新などの元の問題を解決しないことにも注意してください。理想的には、両方を使用して両端をカバーする必要があります。 とにかく、今日はここまでです。これと同じテーマを取り上げたビデオを見たい場合は、これを見てください。 https://www.youtube.com/watch?v=w8ZIoLnh1Dc&embedable=true 読んでいただきありがとうございます。この記事が気に入ったらどうぞ .それは私をサポートする最良の方法の 1 つです。あなたもすることができます また 新しい記事が公開されたときに知りたい場合。 共有する ニュースレターにサインアップする Twitterで私に従ってください 最初に公開された austingil.com .