Si alguna vez ha utilizado la API de JavaScript para mejorar el envío de un formulario, es muy probable que haya introducido accidentalmente un error de solicitud duplicada/condición de carrera. Hoy, lo guiaré a través del problema y mis recomendaciones para evitarlo. fetch (Video al final si lo prefieres) Consideremos una muy básica con una sola entrada y un botón de envío. formulario HTML <form method="post"> <label for="name">Name</label> <input id="name" name="name" /> <button>Submit</button> </form> Cuando presionamos el botón de enviar, el navegador actualizará toda la página. Observe cómo se recarga el navegador después de hacer clic en el botón Enviar. La actualización de la página no siempre es la experiencia que queremos ofrecer a nuestros usuarios, por lo que una alternativa común es usar para agregar un detector de eventos al evento "enviar" del formulario, evitar el comportamiento predeterminado y enviar los datos del formulario mediante la API . JavaScript fetch Un enfoque simplista podría parecerse al siguiente ejemplo. Después de que se monte la página (o el componente), tomamos el nodo DOM del formulario, agregamos un detector de eventos que construye una solicitud usando el formulario , , y , y al final del controlador, llamamos al método del evento. fetch acción método datos 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(); } Ahora, antes de que cualquier hotshot de JavaScript comience a twittearme sobre GET vs. POST y solicite el cuerpo y y cualquier otra cosa, déjame decirte, lo sé. Mantengo la solicitud deliberadamente simple porque ese no es el enfoque principal. Tipo de contenido fetch El problema clave aquí es . Este método evita que el navegador realice el comportamiento predeterminado de cargar la nueva página y enviar el formulario. event.preventDefault() Ahora, si miramos la pantalla y presionamos enviar, podemos ver que la página no se vuelve a cargar, pero sí vemos la solicitud HTTP en nuestra pestaña de red. Tenga en cuenta que el navegador no recarga la página completa. Desafortunadamente, al usar JavaScript para evitar el comportamiento predeterminado, en realidad hemos introducido un error que el comportamiento predeterminado del navegador no tiene. Cuando usamos simple y presiona el botón Enviar un montón de veces muy rápido, notará que todas las solicitudes de la red, excepto la más reciente, se vuelven rojas. Esto indica que se cancelaron y solo se acepta la solicitud más reciente. HTML Si lo comparamos con el ejemplo de JavaScript, veremos que todas las solicitudes se envían y todas están completas sin que se cancele ninguna. Esto puede ser un problema porque, aunque cada solicitud puede tomar una cantidad de tiempo diferente, podrían resolverse en un orden diferente al que se iniciaron. Esto significa que si agregamos funcionalidad a la resolución de esas solicitudes, es posible que tengamos algún comportamiento inesperado. Como ejemplo, podríamos crear una variable para incrementar por cada solicitud (" "). Cada vez que ejecutamos la función , podemos incrementar el recuento total y capturar el número actual para realizar un seguimiento de la solicitud actual (" "). totalRequestCount handleSubmit thisRequestNumber Cuando se resuelve una solicitud , podemos registrar su número correspondiente en la consola. 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(); } Ahora, si presionamos el botón Enviar varias veces, es posible que veamos diferentes números impresos en la consola fuera de orden: 2, 3, 1, 4, 5. Depende de la velocidad de la red, pero creo que todos podemos estar de acuerdo. que esto no es lo ideal. Considere un escenario en el que un usuario activa varias solicitudes en una sucesión cercana y, al finalizar, su aplicación actualiza la página con sus cambios. En última instancia, el usuario podría ver información inexacta debido a que las solicitudes se resolvieron fuera de orden. fetch Esto no es un problema en el mundo sin JavaScript porque el navegador cancela cualquier solicitud anterior y carga la página después de que se completa la solicitud más reciente, cargando la versión más actualizada. Pero las actualizaciones de página no son tan atractivas. La buena noticia para los amantes de JavaScript es que podemos tener un ¡Y una interfaz de usuario consistente! experiencia de usuario atractiva Sólo tenemos que hacer un poco más de trabajo preliminar. Si observa la documentación de la API , verá que es posible cancelar una recuperación utilizando un y la propiedad de las opciones . Se ve algo como esto: fetch AbortController signal fetch const controller = new AbortController(); fetch(url, { signal: controller.signal }); Al proporcionar la señal de a la solicitud , podemos cancelar la solicitud en cualquier momento en que se active el método de . AbortContoller fetch abort AbortContoller Puede ver un ejemplo más claro en la consola de JavaScript. Intente crear un , inicie la solicitud y luego ejecute inmediatamente el método . AbortController fetch abort const controller = new AbortController(); fetch('', { signal: controller.signal }); controller.abort() Debería ver inmediatamente una excepción impresa en la consola. En los navegadores Chromium, debería decir: "DOMException no detectada (en promesa): el usuario canceló una solicitud". Y si explora la pestaña Red, debería ver una solicitud fallida con el texto de estado "(cancelado)". Con eso en mente, podemos agregar un al controlador de envío de nuestro formulario. La lógica será la siguiente: AbortController Primero, busque un para cualquier solicitud anterior. Si existe uno, cancelarlo. AbortController A continuación, cree un para la solicitud actual que se puede cancelar en solicitudes posteriores. AbortController Finalmente, cuando se resuelve una solicitud, elimine su correspondiente. AbortController Hay varias formas de hacer esto, pero usaré un para almacenar las relaciones entre cada nodo DOM enviado y su respectivo . Cuando se envía un formulario, podemos verificar y actualizar en consecuencia. 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); } La clave es poder asociar un controlador de aborto con su formulario correspondiente. Usar el nodo DOM del formulario como clave de es una forma conveniente de hacerlo. WeakMap Con eso en su lugar, podemos agregar la señal de a la solicitud , abortar cualquier controlador anterior, agregar otros nuevos y eliminarlos al finalizar. AbortController fetch Con suerte, todo eso tiene sentido. Ahora, si presionamos el botón de envío de ese formulario varias veces, podemos ver que todas las solicitudes de API, excepto la más reciente, se cancelan. Esto significa que cualquier función que responda a esa respuesta HTTP se comportará más como cabría esperar. Ahora, si usamos la misma lógica de conteo y registro que tenemos arriba, podemos presionar el botón Enviar siete veces y veríamos seis excepciones (debido al ) y un registro de "7" en la consola. AbortController Si volvemos a enviar y damos suficiente tiempo para que se resuelva la solicitud, veremos "8" en la consola. Y si presionamos el botón Enviar varias veces, nuevamente, continuaremos viendo las excepciones y el recuento final de solicitudes en el orden correcto. Si desea agregar más lógica para evitar ver DOMExceptions en la consola cuando se cancela una solicitud, puede agregar un bloque después de su solicitud y verificar si el nombre del error coincide con " ": .catch() fetch 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 }); Clausura Toda esta publicación se centró en los formularios mejorados con JavaScript, pero probablemente sea una buena idea incluir un cada vez que cree una solicitud . Realmente es una lástima que no esté integrado en la API. Pero con suerte, esto le muestra un buen método para incluirlo. AbortController fetch También vale la pena mencionar que este enfoque no evita que el usuario envíe spam al botón de envío varias veces. Todavía se puede hacer clic en el botón y la solicitud aún se dispara, solo proporciona una forma más consistente de manejar las respuestas. Desafortunadamente, si un usuario spam a un botón de envío, esas solicitudes aún irían a su backend y podrían consumir una gran cantidad de recursos innecesarios. envía Algunas soluciones ingenuas pueden ser deshabilitar el botón de enviar, usando un , o solo crear nuevas solicitudes después de que se resuelvan las anteriores. No me gustan estas opciones porque se basan en ralentizar la experiencia del usuario y solo funcionan en el lado del cliente. rebote No abordan el abuso a través de solicitudes programadas. Para abordar el abuso de demasiadas solicitudes a su servidor, probablemente desee configurar algunos . Eso va más allá del alcance de esta publicación, pero vale la pena mencionarlo. limitación de velocidad También vale la pena mencionar que la limitación de velocidad no resuelve el problema original de solicitudes duplicadas, condiciones de carrera y actualizaciones de IU inconsistentes. Lo ideal es que utilicemos ambos para cubrir ambos extremos. De todos modos, eso es todo lo que tengo por hoy. Si quieres ver un video que trata sobre este mismo tema, mira esto. https://www.youtube.com/watch?v=w8ZIoLnh1Dc&embedable=true Muchas Gracias Por Leer. Si te ha gustado este artículo, por favor . Es una de las mejores formas de apoyarme. Tú también puedes o si quieres saber cuando se publican nuevos artículos. Compártelo Inscríbete a mi boletín de noticias Sigueme en Twitter Publicado originalmente en austingil.com .