paint-brush
Hướng dẫn về cách hủy các yêu cầu tìm nạp trùng lặp trong các biểu mẫu nâng cao của JavaScripttừ tác giả@austingil
2,570 lượt đọc
2,570 lượt đọc

Hướng dẫn về cách hủy các yêu cầu tìm nạp trùng lặp trong các biểu mẫu nâng cao của JavaScript

từ tác giả Austin Gil8m2023/02/10
Read on Terminal Reader

dài quá đọc không nổi

Rất có thể bạn đã vô tình đưa ra lỗi trùng lặp yêu cầu/điều kiện chủng tộc. Hôm nay, tôi sẽ hướng dẫn bạn về vấn đề này và các đề xuất của tôi để tránh nó. Vấn đề chính ở đây là`event.preventDefault()`. Phương pháp này ngăn trình duyệt thực hiện hành vi mặc định là tải trang mới và gửi biểu mẫu.
featured image - Hướng dẫn về cách hủy các yêu cầu tìm nạp trùng lặp trong các biểu mẫu nâng cao của JavaScript
Austin Gil HackerNoon profile picture

Nếu bạn đã từng sử dụng API fetch JavaScript để tăng cường khả năng gửi biểu mẫu, thì rất có thể bạn đã vô tình đưa vào lỗi trùng lặp yêu cầu/điều kiện chủng tộc. Hôm nay, tôi sẽ hướng dẫn bạn về vấn đề này và các đề xuất của tôi để tránh nó.


(Video ở cuối nếu bạn thích điều đó)


Hãy xem xét một điều rất cơ bản biểu mẫu HTML với một đầu vào duy nhất và một nút gửi.


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


Khi chúng tôi nhấn nút gửi, trình duyệt sẽ làm mới toàn bộ trang.


Lưu ý cách trình duyệt tải lại sau khi nhấp vào nút gửi.


Làm mới trang không phải lúc nào cũng là trải nghiệm mà chúng tôi muốn cung cấp cho người dùng của mình, vì vậy, một giải pháp thay thế phổ biến là sử dụng JavaScript để thêm trình xử lý sự kiện vào sự kiện “gửi” của biểu mẫu, ngăn hành vi mặc định và gửi dữ liệu biểu mẫu bằng cách sử dụng API fetch .


Một cách tiếp cận đơn giản có thể giống như ví dụ dưới đây.


Sau khi trang (hoặc thành phần) gắn kết, chúng tôi lấy nút DOM của biểu mẫu, thêm trình xử lý sự kiện để tạo yêu cầu fetch bằng biểu mẫu hoạt động , phương pháp , Và dữ liệu và ở cuối trình xử lý, chúng tôi gọi phương thức preventDefault() của sự kiện.


 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(); }


Bây giờ, trước khi bất kỳ hotshot JavaScript nào bắt đầu tweet với tôi về GET so với POST và nội dung yêu cầu và Loại nội dung và bất cứ điều gì khác, hãy để tôi nói, tôi biết. Tôi cố ý giữ cho yêu cầu fetch đơn giản vì đó không phải là trọng tâm chính.


Vấn đề chính ở đây là event.preventDefault() . Phương pháp này ngăn trình duyệt thực hiện hành vi mặc định là tải trang mới và gửi biểu mẫu.


Bây giờ, nếu chúng tôi nhìn vào màn hình và nhấn gửi, chúng tôi có thể thấy rằng trang không tải lại, nhưng chúng tôi thấy yêu cầu HTTP trong tab mạng của mình.


Lưu ý rằng trình duyệt không tải lại toàn bộ trang.


Thật không may, bằng cách sử dụng JavaScript để ngăn hành vi mặc định, chúng tôi đã thực sự đưa ra một lỗi mà hành vi mặc định của trình duyệt không có.


Khi chúng ta sử dụng đồng bằng HTML và bạn đập nút gửi nhiều lần thật nhanh, bạn sẽ nhận thấy rằng tất cả các yêu cầu mạng ngoại trừ yêu cầu gần đây nhất đều chuyển sang màu đỏ. Điều này cho thấy rằng chúng đã bị hủy và chỉ yêu cầu gần đây nhất mới được thực hiện.


Nếu chúng ta so sánh điều đó với ví dụ JavaScript, chúng ta sẽ thấy rằng tất cả các yêu cầu đều được gửi và tất cả chúng đều hoàn tất mà không có yêu cầu nào bị hủy.


Đây có thể là một vấn đề vì mặc dù mỗi yêu cầu có thể mất một khoảng thời gian khác nhau nhưng chúng có thể được giải quyết theo một thứ tự khác với thứ tự ban đầu. Điều này có nghĩa là nếu chúng tôi thêm chức năng để giải quyết các yêu cầu đó, chúng tôi có thể có một số hành vi không mong muốn.


Ví dụ: chúng ta có thể tạo một biến để tăng cho mỗi yêu cầu (" totalRequestCount "). Mỗi khi chúng tôi chạy hàm handleSubmit , chúng tôi có thể tăng tổng số lượng cũng như nắm bắt số hiện tại để theo dõi yêu cầu hiện tại (" thisRequestNumber ").


Khi một yêu cầu fetch được giải quyết, chúng tôi có thể ghi số tương ứng của nó vào bảng điều khiển.


 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(); }


Bây giờ, nếu chúng ta đập nút gửi đó nhiều lần, chúng ta có thể thấy các số khác nhau được in ra bảng điều khiển không theo thứ tự: 2, 3, 1, 4, 5. Điều này phụ thuộc vào tốc độ mạng, nhưng tôi nghĩ tất cả chúng ta đều có thể đồng ý rằng đây không phải là lý tưởng.


Hãy xem xét một tình huống trong đó người dùng kích hoạt một số yêu cầu fetch liên tiếp và sau khi hoàn thành, ứng dụng của bạn sẽ cập nhật trang với các thay đổi của họ. Cuối cùng, người dùng có thể thấy thông tin không chính xác do các yêu cầu được giải quyết không theo thứ tự.


Đây không phải là vấn đề trong thế giới không có JavaScript vì trình duyệt hủy mọi yêu cầu trước đó và tải trang sau khi yêu cầu gần đây nhất hoàn tất, tải phiên bản cập nhật nhất. Nhưng việc làm mới trang không hấp dẫn bằng.


Tin vui cho những người yêu thích JavaScript là chúng ta có thể có cả hai trải nghiệm người dùng gợi cảm VÀ một giao diện người dùng nhất quán!


Chúng ta chỉ cần làm thêm một chút công việc.


Nếu bạn xem tài liệu API fetch , bạn sẽ thấy rằng có thể hủy bỏ tìm nạp bằng AbortController và thuộc tính signal của các tùy chọn fetch . Nó trông giống như thế này:


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


Bằng cách cung cấp tín hiệu của AbortContoller cho yêu cầu fetch , chúng tôi có thể hủy yêu cầu bất kỳ lúc nào phương thức abort của AbortContoller được kích hoạt.


Bạn có thể xem một ví dụ rõ ràng hơn trong bảng điều khiển JavaScript. Hãy thử tạo một AbortController , bắt đầu yêu cầu fetch , sau đó thực hiện ngay phương thức abort .


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


Bạn sẽ thấy ngay một ngoại lệ được in ra bảng điều khiển. Trong các trình duyệt Chromium, nó sẽ nói, "Không bắt được (trong lời hứa) DOMException: Người dùng đã hủy bỏ một yêu cầu." Và nếu bạn khám phá tab Mạng, bạn sẽ thấy một yêu cầu không thành công với Văn bản trạng thái “(đã hủy).”


Các công cụ dành cho nhà phát triển Chrome được mở vào mạng khi mở bảng điều khiển JavaScript. Trong bảng điều khiển là mã "const controller = new AbortController();fetch('', { signal: controller.signal });controller.abort()", theo sau là ngoại lệ, "Uncaught (in promise) DOMException: The người dùng đã hủy bỏ một yêu cầu." Trong mạng, có một yêu cầu "localhost" với văn bản trạng thái "(đã hủy)"

Với ý nghĩ đó, chúng ta có thể thêm một AbortController vào trình xử lý gửi biểu mẫu của chúng ta. Logic sẽ như sau:


  • Trước tiên, hãy kiểm tra AbortController cho bất kỳ yêu cầu nào trước đó. Nếu một tồn tại, hủy bỏ nó.


  • Tiếp theo, tạo một AbortController cho yêu cầu hiện tại có thể bị hủy bỏ trong các yêu cầu tiếp theo.


  • Cuối cùng, khi một yêu cầu được giải quyết, hãy xóa AbortController tương ứng của nó.


Có một số cách để làm điều này, nhưng tôi sẽ sử dụng một WeakMap để lưu trữ các mối quan hệ giữa mỗi nút DOM <form> đã gửi và AbortController tương ứng của nó. Khi một biểu mẫu được gửi, chúng tôi có thể kiểm tra và cập nhật WeakMap tương ứng.


 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); }


Điều quan trọng là có thể liên kết bộ điều khiển hủy bỏ với biểu mẫu tương ứng của nó. Sử dụng nút DOM của biểu mẫu làm khóa WeakMap là một cách thuận tiện để thực hiện điều đó.


Với điều đó, chúng ta có thể thêm tín hiệu của AbortController vào yêu cầu fetch , hủy bỏ mọi bộ điều khiển trước đó, thêm bộ điều khiển mới và xóa chúng sau khi hoàn thành.


Hy vọng rằng, tất cả đều có ý nghĩa.


Bây giờ, nếu chúng ta đập nút gửi của biểu mẫu đó nhiều lần, chúng ta có thể thấy rằng tất cả các yêu cầu API ngoại trừ yêu cầu gần đây nhất đều bị hủy.


Điều này có nghĩa là bất kỳ chức năng nào đáp ứng phản hồi HTTP đó sẽ hoạt động tốt hơn như bạn mong đợi.


Bây giờ, nếu chúng ta sử dụng cùng logic đếm và ghi nhật ký mà chúng ta có ở trên, chúng ta có thể đập nút gửi bảy lần và sẽ thấy sáu ngoại lệ (do AbortController ) và một nhật ký của “7” trong bảng điều khiển.


Nếu chúng tôi gửi lại và có đủ thời gian để yêu cầu được giải quyết, chúng tôi sẽ thấy “8” trong bảng điều khiển. Và nếu chúng tôi đập nút gửi nhiều lần, chúng tôi sẽ tiếp tục thấy các trường hợp ngoại lệ và số lượng yêu cầu cuối cùng theo đúng thứ tự.


Nếu bạn muốn thêm một số logic để tránh nhìn thấy DOMExceptions trong bảng điều khiển khi yêu cầu bị hủy bỏ, bạn có thể thêm .catch() sau yêu cầu fetch của mình và kiểm tra xem tên lỗi có khớp với “ 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 });

Đóng cửa

Toàn bộ bài đăng này tập trung vào các biểu mẫu được tăng cường JavaScript, nhưng có lẽ nên bao gồm một AbortController bất kỳ khi nào bạn tạo một yêu cầu fetch . Thật tệ là nó chưa được tích hợp vào API. Nhưng hy vọng, điều này cho bạn thấy một phương pháp tốt để đưa nó vào.


Điều đáng nói là phương pháp này không ngăn người dùng spam nút gửi nhiều lần. Nút vẫn có thể nhấp được và yêu cầu vẫn kích hoạt, nó chỉ cung cấp cách xử lý phản hồi nhất quán hơn.


Thật không may, nếu người dùng spam nút gửi, những yêu cầu đó vẫn sẽ được chuyển đến chương trình phụ trợ của bạn và có thể sử dụng tiêu tốn nhiều tài nguyên không cần thiết.


Một số giải pháp ngây thơ có thể vô hiệu hóa nút gửi, sử dụng ra mắt hoặc chỉ tạo các yêu cầu mới sau khi các yêu cầu trước đó được giải quyết. Tôi không thích các tùy chọn này vì chúng làm chậm trải nghiệm của người dùng và chỉ hoạt động ở phía máy khách.


Họ không giải quyết vấn đề lạm dụng thông qua các yêu cầu theo kịch bản.


Để giải quyết tình trạng lạm dụng từ quá nhiều yêu cầu đến máy chủ của bạn, có thể bạn sẽ muốn thiết lập một số giới hạn tỷ lệ . Điều đó vượt ra ngoài phạm vi của bài đăng này, nhưng nó đáng được đề cập.


Cũng cần lưu ý rằng giới hạn tốc độ không giải quyết được vấn đề ban đầu về yêu cầu trùng lặp, điều kiện cuộc đua và cập nhật giao diện người dùng không nhất quán. Tốt nhất, chúng ta nên sử dụng cả hai để che cả hai đầu.


Dù sao, đó là tất cả những gì tôi có cho ngày hôm nay. Nếu bạn muốn xem video có cùng chủ đề này, hãy xem video này.

Cảm ơn bạn rất nhiều vì đã đọc. Nếu bạn thích bài viết này, xin vui lòng chia sẻ nó . Đó là một trong những cách tốt nhất để hỗ trợ tôi. Bạn cũng có thể Hãy đăng ký để nhận thư mới từ tôi hoặc theo dõi tôi trên Twitter nếu bạn muốn biết khi bài viết mới được xuất bản.


Được xuất bản lần đầu vào austingil.com .