332

There is a new API for making requests from JavaScript: fetch(). Is there any built in mechanism for canceling these requests in-flight?

1

8 Answers 8

539

TL/DR:

fetch now supports a signal parameter as of 20 September 2017, but not all browsers seem support this at the moment.

2020 UPDATE: Most major browsers (Edge, Firefox, Chrome, Safari, Opera, and a few others) support the feature, which has become part of the DOM living standard. (as of 5 March 2020)

This is a change we will be seeing very soon though, and so you should be able to cancel a request by using an AbortControllers AbortSignal.

Long Version

How to:

The way it works is this:

Step 1: You create an AbortController (For now I just used this)

const controller = new AbortController() 

Step 2: You get the AbortControllers signal like this:

const signal = controller.signal 

Step 3: You pass the signal to fetch like so:

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

Step 4: Just abort whenever you need to:

controller.abort(); 

Here's an example of how it would work (works on Firefox 57+):

// 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() }
<h1>Example of fetch abort</h1> <hr> <button onclick="beginFetching();">Begin</button> <button onclick="abortFetching();">Abort</button>

Sources:

Sign up to request clarification or add additional context in comments.

14 Comments

This answer is correct and should be upvoted. But I took the liberty of making some edits to the code snippet, because as-is it wasn’t actually working in Firefox 57+ — the shim seemed to be causing it to fail (“Err: TypeError: 'signal' member of RequestInit does not implement interface AbortSignal.”) and there seems to be some problem with the cert for slowwly.robertomurray.co.uk (“This server could not prove that it is slowwly.robertomurray.co.uk; its security certificate is from *.herokuapp.com.”), so I changed it to just use slowwly.robertomurray.co.uk (plain http).
This is pure StackOverflow gold, thanks for the concise writeup! And the bugtracker links as well!
Now all modern browsers support it. developer.mozilla.org/en-US/docs/Web/API/AbortController/abort see table in the bottom
Thanks but I still have a question, should we change the signal back to true for the next fetch manually??
@akshaykishore Once an instance of AbortController is used, we need to create a new instance to reset the value. dev.to/beejluig/cancel-fetch-with-abortcontroller-2435
|
42

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

works in edge 16 (2017-10-17), firefox 57 (2017-11-14), desktop safari 11.1 (2018-03-29), ios safari 11.4 (2018-03-29), chrome 67 (2018-05-29), and later.


on older browsers, you can use github's whatwg-fetch polyfill and AbortController polyfill. you can detect older browsers and use the polyfills conditionally, too:

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 

3 Comments

If using github's fetch polyfill, this is possible to do with it, just follow the instructions on their readme: github.com/github/fetch#aborting-requests
@FábioSantos Should your comment be on the question, or as an answer in it's own right? It doesn't look specific to my answer.
Just a note for people who are using the github fetch polyfill. I thought it was relevant to your answer because AFAIK it's the most popular fetch polyfill available, and it polyfills the function you're using, fetch. A lot of people will be using this polyfill because of old browsers. I found it important to mention because people just assume polyfills fix everything, but this particular one doesn't try to polyfill AbortController. They'd try to use AbortController thinking it's going to be polyfilled in old browsers, and boom, there's an exception in a corner case and only on old browsers.
5

As for now there is no proper solution, as @spro says.

However, if you have an in-flight response and are using ReadableStream, you can close the stream to cancel the request.

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

Comments

5

As of Feb 2018, fetch() can be cancelled with the code below on Chrome (read Using Readable Streams to enable Firefox support). No error is thrown for catch() to pick up, and this is a temporary solution until AbortController is fully adopted.

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

1 Comment

This is NOT what the OP was asking for. They want to cancel the fetch not the reader. Fetch's promise does not resolve until AFTER the request has finished, which is too late to cancel the request to the server.
4

Let's 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; } 

Please have in mind that the code is not tested! Let me know if you have tested it and something didn't work. It may give you warnings that you try to overwrite the 'fetch' function from the JavaScript official library.

Comments

2

To set an abort timeout you can use AbortSignal interface and its .timeout(ms) static method:

fetch("data.json", { signal: AbortSignal.timeout(3000) // Abort request after 3 sec. }) // ... 

(async () => { try { const req1 = await fetch("https://jsonplaceholder.typicode.com/todos/1", { signal: AbortSignal.timeout(0) // Demo. Use a saner value like i.e: 3000 }); const json1 = await req1.json(); console.log(json1); } catch (err) { console.error(err); // TimeoutError: signal timed out } })();


To manually abort a fetch (like clicking on a Cancel request button) - in that case (as suggested) use AbortController constructor and its .abort() method

const ac = new AbortController(); fetch("data.json", { signal: ac.signal }) // ... ac.abort(); // Abort when needed 

(async () => { try { const ac = new AbortController(); const req2 = await fetch("https://jsonplaceholder.typicode.com/todos/1", { signal: ac.signal }); ac.abort(); // Or run manually, i.e: on a button click // Will end here since it skips to the Catch block const json2 = await req2.json(); console.log(json2); } catch (err) { console.error(err); // AbortError: The user aborted a request. } })();

Comments

-1

This works in browser and nodejs Live browser demo

const cpFetch= require('cp-fetch'); const url= 'https://run.mocky.io/v3/753aa609-65ae-4109-8f83-9cfe365290f0?mocky-delay=3s'; const chain = cpFetch(url, {timeout: 10000}) .then(response => response.json()) .then(data => console.log(`Done: `, data), err => console.log(`Error: `, err)) setTimeout(()=> chain.cancel(), 1000); // abort the request after 1000ms 

Comments

-1

Easy typescripted version (fetch gets aborted):

export async function fetchWithTimeout(url: RequestInfo, options?: RequestInit, timeout?: number) { return new Promise<Response>((resolve, reject) => { const controller = new AbortController(); const signal = controller.signal; const timeoutId = setTimeout(() => { console.log('TIMEOUT'); reject('Timeout'); // Cancel fetch in progress controller.abort(); }, timeout ?? 5 * 1000); fetch(url, { ...options, signal }) .then((response) => { clearTimeout(timeoutId); resolve(response); }) .catch((e) => reject(e)); }); } 

Maybe you need a polyfill (e.g. IE11):

https://polyfill.io/v3/polyfill.min.js?features=AbortController 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.