Как отменить запрос HTTP 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 .

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

Если вы используете выборочный 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();
        })
      }
    }
  }
}

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.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.