paint-brush
Um guia sobre como cancelar solicitações de busca duplicadas em formulários aprimorados de JavaScriptpor@austingil
1,862 leituras
1,862 leituras

Um guia sobre como cancelar solicitações de busca duplicadas em formulários aprimorados de JavaScript

por Austin Gil8m2023/02/10
Read on Terminal Reader

Muito longo; Para ler

Há uma boa chance de você ter introduzido acidentalmente um bug de solicitação duplicada/condição de corrida. Hoje, vou orientá-lo sobre o problema e minhas recomendações para evitá-lo. A questão chave aqui é o `event.preventDefault()`. Esse método impede que o navegador execute o comportamento padrão de carregar a nova página e enviar o formulário.
featured image - Um guia sobre como cancelar solicitações de busca duplicadas em formulários aprimorados de JavaScript
Austin Gil HackerNoon profile picture

Se você já usou a API fetch de JavaScript para aprimorar o envio de um formulário, há uma boa chance de ter introduzido acidentalmente um bug de solicitação duplicada/condição de corrida. Hoje, vou orientá-lo sobre o problema e minhas recomendações para evitá-lo.


(Vídeo no final se preferir)


Vamos considerar um muito básico formulário HTML com uma única entrada e um botão enviar.


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


Quando pressionamos o botão enviar, o navegador atualiza toda a página.


Observe como o navegador é recarregado depois que o botão enviar é clicado.


A atualização da página nem sempre é a experiência que queremos oferecer aos nossos usuários, então uma alternativa comum é usar JavaScript para adicionar um ouvinte de evento ao evento “enviar” do formulário, evitar o comportamento padrão e enviar os dados do formulário usando a API fetch .


Uma abordagem simplista pode se parecer com o exemplo abaixo.


Depois que a página (ou componente) é montada, pegamos o nó DOM do formulário, adicionamos um ouvinte de evento que constrói uma solicitação fetch usando o formulário Ação , método , e dados , e no final do manipulador, chamamos o método preventDefault() do evento.


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


Agora, antes que qualquer figurão do JavaScript comece a twittar para mim sobre GET vs. POST e solicitar corpo e Tipo de conteúdo e seja o que for, deixe-me apenas dizer, eu sei. Estou mantendo a solicitação fetch deliberadamente simples porque esse não é o foco principal.


A questão chave aqui é o event.preventDefault() . Esse método impede que o navegador execute o comportamento padrão de carregar a nova página e enviar o formulário.


Agora, se olharmos para a tela e clicarmos em enviar, podemos ver que a página não é recarregada, mas vemos a solicitação HTTP em nossa guia de rede.


Observe que o navegador não recarrega a página inteira.


Infelizmente, ao usar JavaScript para evitar o comportamento padrão, introduzimos um bug que não existe no comportamento padrão do navegador.


Quando usamos simples HTML e você apertar o botão enviar várias vezes rapidamente, notará que todas as solicitações de rede, exceto a mais recente, ficam vermelhas. Isso indica que eles foram cancelados e apenas a solicitação mais recente é atendida.


Se compararmos com o exemplo do JavaScript, veremos que todas as requisições são enviadas, e todas são completadas sem que nenhuma seja cancelada.


Isso pode ser um problema porque, embora cada solicitação possa levar um tempo diferente, elas podem ser resolvidas em uma ordem diferente da que foram iniciadas. Isso significa que, se adicionarmos funcionalidade à resolução dessas solicitações, poderemos ter algum comportamento inesperado.


Como exemplo, poderíamos criar uma variável para incrementar a cada requisição (“ totalRequestCount “). Sempre que executamos a função handleSubmit , podemos incrementar a contagem total, bem como capturar o número atual para rastrear a solicitação atual (“ thisRequestNumber “).


Quando uma solicitação fetch é resolvida, podemos registrar seu número correspondente no console.


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


Agora, se apertarmos o botão enviar várias vezes, podemos ver números diferentes impressos no console fora de ordem: 2, 3, 1, 4, 5. Depende da velocidade da rede, mas acho que todos podemos concordar que isso não é o ideal.


Considere um cenário em que um usuário aciona várias solicitações fetch em sucessão próxima e, após a conclusão, seu aplicativo atualiza a página com as alterações. O usuário pode, em última análise, ver informações imprecisas devido a solicitações resolvidas fora de ordem.


Isso não é um problema no mundo não-JavaScript porque o navegador cancela qualquer solicitação anterior e carrega a página após a conclusão da solicitação mais recente, carregando a versão mais atualizada. Mas as atualizações de página não são tão atraentes.


A boa notícia para os amantes de JavaScript é que podemos ter um experiência sexy do usuário E uma IU consistente!


Só precisamos fazer um pouco mais de trabalho braçal.


Se você examinar a documentação da API fetch , verá que é possível abortar uma busca usando um AbortController e a propriedade signal das opções fetch . Parece algo assim:


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


Ao fornecer o sinal do AbortContoller para a solicitação fetch , podemos cancelar a solicitação sempre que o método abort do AbortContoller for acionado.


Você pode ver um exemplo mais claro no console JavaScript. Tente criar um AbortController , iniciando a solicitação fetch e, em seguida, executando imediatamente o método abort .


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


Você deve ver imediatamente uma exceção impressa no console. Nos navegadores Chromium, deve aparecer “Uncaught (in promise) DOMException: The user aborted a request.” E se você explorar a guia Rede, deverá ver uma solicitação com falha com o texto de status “(cancelado)”.


As ferramentas de desenvolvimento do Chrome são abertas na rede com o console JavaScript aberto. No console está o código "const controller = new AbortController();fetch('', { signal: controller.signal });controller.abort()", seguido pela exceção, "Uncaught (in promise) DOMException: The usuário cancelou uma solicitação." Na rede, há uma solicitação para "localhost" com o texto de status "(cancelado)"

Com isso em mente, podemos adicionar um AbortController ao manipulador de envio do nosso formulário. A lógica será a seguinte:


  • Primeiro, verifique se há um AbortController para quaisquer solicitações anteriores. Se existir, aborte-o.


  • Em seguida, crie um AbortController para a solicitação atual que pode ser anulada em solicitações subsequentes.


  • Por fim, quando uma solicitação for resolvida, remova seu AbortController correspondente.


Existem várias maneiras de fazer isso, mas usarei um WeakMap para armazenar relacionamentos entre cada nó DOM <form> enviado e seu respectivo AbortController . Quando um formulário é enviado, podemos verificar e atualizar o WeakMap de acordo.


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


O principal é ser capaz de associar um controlador de aborto ao seu formulário correspondente. Usar o nó DOM do formulário como a chave do WeakMap é uma maneira conveniente de fazer isso.


Com isso definido, podemos adicionar o sinal do AbortController à solicitação fetch , abortar quaisquer controladores anteriores, adicionar novos e excluí-los após a conclusão.


Esperançosamente, tudo isso faz sentido.


Agora, se esmagarmos o botão de envio desse formulário várias vezes, podemos ver que todas as solicitações de API, exceto a mais recente, são canceladas.


Isso significa que qualquer função que responda a essa resposta HTTP se comportará mais conforme o esperado.


Agora, se usarmos a mesma lógica de contagem e registro que temos acima, podemos esmagar o botão enviar sete vezes e veremos seis exceções (devido ao AbortController ) e um log de “7” no console.


Se enviarmos novamente e dermos tempo suficiente para a solicitação ser resolvida, veremos “8” no console. E se pressionarmos o botão enviar várias vezes, novamente, continuaremos a ver as exceções e a contagem final de solicitações na ordem correta.


Se você deseja adicionar um pouco mais de lógica para evitar ver DOMExceptions no console quando uma solicitação é abortada, você pode adicionar um bloco .catch() após sua solicitação fetch e verificar se o nome do erro corresponde a “ 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 });

fechando

Todo este post foi focado em formulários aprimorados em JavaScript, mas provavelmente é uma boa ideia incluir um AbortController sempre que você criar uma solicitação fetch . É realmente uma pena que ainda não esteja embutido na API. Mas espero que isso mostre um bom método para incluí-lo.


Também vale a pena mencionar que essa abordagem não impede que o usuário envie spam várias vezes para o botão enviar. O botão ainda pode ser clicado e a solicitação ainda é disparada, apenas fornece uma maneira mais consistente de lidar com as respostas.


Infelizmente, se um usuário fizer spam em um botão de envio, essas solicitações ainda vão para o seu back-end e podem consumir um monte de recursos desnecessários.


Algumas soluções ingênuas podem desabilitar o botão enviar, usando um debounce , ou apenas criando novas solicitações após a resolução das anteriores. Não gosto dessas opções porque elas reduzem a velocidade da experiência do usuário e funcionam apenas no lado do cliente.


Eles não abordam o abuso por meio de solicitações com script.


Para resolver o abuso de muitas solicitações ao seu servidor, você provavelmente deseja configurar alguns limitação de taxa . Isso vai além do escopo deste post, mas vale a pena mencionar.


Também vale a pena mencionar que a limitação de taxa não resolve o problema original de solicitações duplicadas, condições de corrida e atualizações de interface do usuário inconsistentes. O ideal é usar os dois para cobrir as duas pontas.


Enfim, isso é tudo que tenho para hoje. Se você quiser assistir a um vídeo que aborda esse mesmo assunto, assista a este.

Muito obrigado pela leitura. Se você gostou deste artigo, por favor Compartilhe . É uma das melhores formas de me apoiar. Você também pode assine minha newsletter ou Siga me no twitter se você quiser saber quando novos artigos são publicados.


Originalmente publicado em austingil.com .