Как отменить запрос HTTP fetch ()?

201

Существует новый API для выполнения запросов из JavaScript: fetch (). Есть ли встроенный механизм для отмены этих запросов в полете?

Сэм Ли
источник
можете сослаться на davidwalsh.name/cancel-fetch также
Ганеш Пхирке

Ответы:

282

TL / DR:

fetchтеперь поддерживает signalпараметр по состоянию на 20 сентября 2017 года, но не все браузеры в настоящее время поддерживают это .

ОБНОВЛЕНИЕ 2020: большинство основных браузеров (Edge, Firefox, Chrome, Safari, Opera и некоторые другие) поддерживают функцию , которая стала частью стандарта жизни DOM . (по состоянию на 5 марта 2020 года)

Это изменение мы увидим очень скоро, и поэтому вы сможете отменить запрос, используя AbortControllers AbortSignal.

Длинная версия

Как:

Вот как это работает:

Шаг 1 : Вы создаете AbortController(пока я только что использовал это )

const controller = new AbortController()

Шаг 2 : Вы получаете AbortControllerсигнал s следующим образом:

const signal = controller.signal

Шаг 3 : Вы передаете, signalчтобы получить вот так:

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

Шаг 4 : Просто прерывайте всякий раз, когда вам нужно:

controller.abort();

Вот пример того, как это будет работать (работает на 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>

Источники:

  • Финальная версия AbortController была добавлена ​​в спецификацию DOM
  • Соответствующий PR для выборки спецификации теперь объединены.
  • Ошибки браузера, отслеживающие реализацию AbortController, доступны здесь: Firefox: # 1378342 , Chromium: # 750599 , WebKit: # 174980 , Edge: # 13009916 .
SudoPlz
источник
2
Этот ответ является правильным и должен быть одобрен. Но я позволил себе внести некоторые изменения в фрагмент кода, потому что на самом деле он не работал в Firefox 57+ - кажется, что оболочка не работает ( «Err: TypeError: 'signal” член RequestInit не реализует интерфейс AbortSignal. » ) и, кажется, есть некоторая проблема с сертификатом для slowwly.robertomurray.co.uk ( « Этот сервер не может доказать, что это slowwly.robertomurray.co.uk; его сертификат безопасности от * .herokuapp.com. » ), поэтому я изменил его на использование slowwly.robertomurray.co.uk (обычный http).
sideshowbarker
3
Но теперь он не работает в других браузерах, например, в Chrome AbortController is not defined. В любом случае, это всего лишь подтверждение концепции, по крайней мере, люди с Firefox 57+ могут увидеть, как это работает
SudoPlz
3
Это чистое золото StackOverflow, спасибо за краткую рецензию! И ссылки на багтрекер тоже!
Kjellski
3
Теперь все современные браузеры поддерживают это. developer.mozilla.org/en-US/docs/Web/API/AbortController/abort см. таблицу внизу
Алекс Ивасюв
2
Спасибо, но у меня все еще есть вопрос, должны ли мы изменить сигнал обратно на true для следующей выборки вручную?
Акшай Кишоре
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();

работает в версии 16 (2017-10-17), Firefox 57 (2017-11-14), настольное Safari 11.1 (2018-03-29), IOS Safari 11.4 (2018-03-29), Chrome 67 (2018-05 -29) и позже.


в более старых браузерах вы можете использовать github's polyfill whatwg-fetch и AbortController polyfill . Вы можете обнаружить старые браузеры и использовать полифиллы также условно :

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
источник
Если вы используете выборочный polyfill для github, это можно сделать, просто следуйте инструкциям в их файле readme: github.com/github/fetch#aborting-requests
Fábio Santos
@ FábioSantos Ваш комментарий должен быть по этому вопросу или как ответ сам по себе? Это не выглядит конкретным для моего ответа.
Jayen
Просто примечание для людей, которые используют Github fetch Polyfill. Я подумал, что это уместно для вашего ответа, потому что AFAIK - это самый популярный из доступных выборок polyfill, и он заполняет функцию, которую вы используете, fetch. Многие люди будут использовать этот polyfill из-за старых браузеров. Я считаю важным упомянуть, потому что люди просто предполагают, что полифиллы исправляют все, но этот конкретный не пытается заполнить AbortController. Они пытались использовать AbortController, думая, что в старых браузерах он будет заполнен полиомиелитом, и бум, есть исключение в угловом случае и только в старых браузерах.
Фабио Сантос
5

По состоянию на февраль 2018 года fetch()его можно отменить с помощью приведенного ниже кода в Chrome (см. « Использование читаемых потоков», чтобы включить поддержку Firefox). Никаких ошибок не возникает catch(), и это временное решение, пока оно не AbortControllerбудет полностью принято.

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
источник
2
Это НЕ то, о чем просил ОП. Они хотят отменить выборку, а не читатель. Обещание Fetch не выполняется до ПОСЛЕ того, как запрос завершен, что слишком поздно для отмены запроса к серверу.
Рахли
3

Пока что нет правильного решения, как говорит @spro.

Однако, если у вас есть ответ в полете и вы используете ReadableStream, вы можете закрыть поток, чтобы отменить запрос.

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();
});
штифтик
источник
0

Давайте полифилл:

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

Пожалуйста, имейте в виду, что код не проверен! Дайте мне знать, если вы проверили это и что-то не сработало. Он может предупредить вас, что вы пытаетесь перезаписать функцию 'fetch' из официальной библиотеки JavaScript.

0xC0DEGURU
источник