Como cancelo uma solicitação HTTP fetch ()?

200

Há uma nova API para fazer solicitações do JavaScript: fetch (). Existe algum mecanismo interno para cancelar essas solicitações durante o voo?

Sam Lee
fonte
pode consultar davidwalsh.name/cancel-fetch também
ganesh phirke

Respostas:

281

TL / DR:

fetchagora suporta um signalparâmetro a partir de 20 de setembro de 2017, mas nem todos os navegadores parecem suportar isso no momento .

ATUALIZAÇÃO 2020: A maioria dos principais navegadores (Edge, Firefox, Chrome, Safari, Opera e alguns outros) suportam o recurso , que se tornou parte do padrão de vida do DOM . (a partir de 5 de março de 2020)

Essa é uma mudança que veremos em breve, portanto, você poderá cancelar uma solicitação usando um AbortControllersAbortSignal .

Versão longa

Como:

O modo como funciona é o seguinte:

Etapa 1 : você cria um AbortController(por enquanto eu apenas usei isso )

const controller = new AbortController()

Etapa 2 : você recebe o AbortControllersinal s assim:

const signal = controller.signal

Etapa 3 : você passa a signalbuscar assim:

fetch(urlToFetch, {
    method: 'get',
    signal: signal, // <------ This is our AbortSignal
})

Etapa 4 : basta abortar sempre que precisar:

controller.abort();

Aqui está um exemplo de como isso funcionaria (funciona no Firefox 57+):

<script>
    // Create an instance.
    const controller = new AbortController()
    const signal = controller.signal

    /*
    // Register a listenr.
    signal.addEventListener("abort", () => {
        console.log("aborted!")
    })
    */


    function beginFetching() {
        console.log('Now fetching');
        var urlToFetch = "https://httpbin.org/delay/3";

        fetch(urlToFetch, {
                method: 'get',
                signal: signal,
            })
            .then(function(response) {
                console.log(`Fetch complete. (Not aborted)`);
            }).catch(function(err) {
                console.error(` Err: ${err}`);
            });
    }


    function abortFetching() {
        console.log('Now aborting');
        // Abort.
        controller.abort()
    }

</script>



<h1>Example of fetch abort</h1>
<hr>
<button onclick="beginFetching();">
    Begin
</button>
<button onclick="abortFetching();">
    Abort
</button>

Fontes:

SudoPlz
fonte
2
Esta resposta está correta e deve ser votada. Mas tomei a liberdade de fazer algumas edições no snippet de código, porque, como ele não estava realmente funcionando no Firefox 57+ - o shim parecia estar causando uma falha ( “Err: TypeError: membro 'signal' do RequestInit não implementa a interface AbortSignal. ” ) e parece haver algum problema com o certificado para slowwly.robertomurray.co.uk ( “ Este servidor não pôde provar que é slowwly.robertomurray.co.uk; seu certificado de segurança é de * .herokuapp.com. ” ), então mudei para apenas usar slowwly.robertomurray.co.uk (http simples).
sideshowbarker
3
Mas agora não funciona em outros navegadores, ou seja, no Chrome porque AbortController is not defined. De qualquer forma esta é apenas uma prova de conceito, pelo menos as pessoas com Firefox 57+ pode vê-lo trabalhando
SudoPlz
3
Isso é ouro puro no StackOverflow, obrigado pela descrição concisa! E o bugtracker também liga!
Kjellski # 11/18
3
Agora, todos os navegadores modernos são compatíveis. developer.mozilla.org/pt-BR/docs/Web/API/AbortController/abort veja a tabela na parte inferior
Alex Ivasyuv
2
Obrigado, mas ainda tenho uma pergunta, devemos alterar o sinal novamente para true para a próxima busca manualmente?
Akshay Kishore
20

https://developers.google.com/web/updates/2017/09/abortable-fetch

https://dom.spec.whatwg.org/#aborting-ongoing-activities

// setup AbortController
const controller = new AbortController();
// signal to pass to fetch
const signal = controller.signal;

// fetch as usual
fetch(url, { signal }).then(response => {
  ...
}).catch(e => {
  // catch the abort if you like
  if (e.name === 'AbortError') {
    ...
  }
});

// when you want to abort
controller.abort();

funciona na borda 16 (2017-10-17), firefox 57 (2017-11-14), safari de desktop 11.1 (2018-03-29), ios safari 11.4 (2018-03-29), chrome 67 (2018-05 -29) e mais tarde.


em navegadores mais antigos, você pode usar o polyfill whatwg-fetch do github e o polyfill AbortController . você pode detectar navegadores mais antigos e usar os polyfills condicionalmente também:

import 'abortcontroller-polyfill/dist/abortcontroller-polyfill-only'
import {fetch} from 'whatwg-fetch'

// use native browser implementation if it supports aborting
const abortableFetch = ('signal' in new Request('')) ? window.fetch : fetch
Jayen
fonte
Se utilizando de github buscar polyfill, isso é possível fazer com ele, basta seguir as instruções na sua readme: github.com/github/fetch#aborting-requests
Fábio Santos
@ FábioSantos O seu comentário deve estar sobre a questão ou como resposta por si só? Não parece específico para a minha resposta.
Jayen
Apenas uma observação para pessoas que estão usando o polyfill de busca do github. Eu pensei que era relevante para a sua resposta porque o AFAIK é o polyfill de busca mais popular disponível, e preenche a função que você está usando, busca. Muitas pessoas usarão esse polyfill por causa de navegadores antigos. Achei importante mencionar, porque as pessoas simplesmente supõem que os polyfills consertam tudo, mas esse em particular não tenta polyfill AbortController. Eles tentavam usar o AbortController pensando que seria preenchido em navegadores antigos e, boom, há uma exceção em uma caixa de canto e apenas em navegadores antigos.
Fábio Santos
5

A partir de fevereiro de 2018, fetch()pode ser cancelado com o código abaixo no Chrome (leia Usando fluxos legíveis para ativar o suporte ao Firefox). Nenhum erro é gerado para catch()ser atendido, e esta é uma solução temporária até que AbortControllerseja totalmente adotada.

fetch('YOUR_CUSTOM_URL')
.then(response => {
  if (!response.body) {
    console.warn("ReadableStream is not yet supported in this browser.  See https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream")
    return response;
  }

  // get reference to ReadableStream so we can cancel/abort this fetch request.
  const responseReader = response.body.getReader();
  startAbortSimulation(responseReader);

  // Return a new Response object that implements a custom reader.
  return new Response(new ReadableStream(new ReadableStreamConfig(responseReader)));
})
.then(response => response.blob())
.then(data => console.log('Download ended. Bytes downloaded:', data.size))
.catch(error => console.error('Error during fetch()', error))


// Here's an example of how to abort request once fetch() starts
function startAbortSimulation(responseReader) {
  // abort fetch() after 50ms
  setTimeout(function() {
    console.log('aborting fetch()...');
    responseReader.cancel()
    .then(function() {
      console.log('fetch() aborted');
    })
  },50)
}


// ReadableStream constructor requires custom implementation of start() method
function ReadableStreamConfig(reader) {
  return {
    start(controller) {
      read();
      function read() {
        reader.read().then(({done,value}) => {
          if (done) {
            controller.close();
            return;
          }
          controller.enqueue(value);
          read();
        })
      }
    }
  }
}
AnthumChris
fonte
2
NÃO é isso que o OP estava pedindo. Eles querem cancelar a busca e não o leitor. A promessa da busca não é resolvida até que a solicitação termine, o que é muito tarde para cancelar a solicitação ao servidor.
Rahly 18/08/19
3

Por enquanto, não há solução adequada, como diz o @spro.

No entanto, se você tiver uma resposta a bordo e estiver usando o ReadableStream, poderá fechar o fluxo para cancelar a solicitação.

fetch('http://example.com').then((res) => {
  const reader = res.body.getReader();

  /*
   * Your code for reading streams goes here
   */

  // To abort/cancel HTTP request...
  reader.cancel();
});
Brad
fonte
0

Vamos polyfill:

if(!AbortController){
  class AbortController {
    constructor() {
      this.aborted = false;
      this.signal = this.signal.bind(this);
    }
    signal(abortFn, scope) {
      if (this.aborted) {
        abortFn.apply(scope, { name: 'AbortError' });
        this.aborted = false;
      } else {
        this.abortFn = abortFn.bind(scope);
      }
    }
    abort() {
      if (this.abortFn) {
        this.abortFn({ reason: 'canceled' });
        this.aborted = false;
      } else {
        this.aborted = true;
      }
    }
  }

  const originalFetch = window.fetch;

  const customFetch = (url, options) => {
    const { signal } = options || {};

    return new Promise((resolve, reject) => {
      if (signal) {
        signal(reject, this);
      }
      originalFetch(url, options)
        .then(resolve)
        .catch(reject);
    });
  };

  window.fetch = customFetch;
}

Por favor, tenha em mente que o código não foi testado! Deixe-me saber se você o testou e algo não funcionou. Pode avisar que você tenta sobrescrever a função 'buscar' da biblioteca oficial do JavaScript.

0xC0DEGURU
fonte