When working with the vanilla JavaScript fetch()
API, aborting a request is not too intuitive.
Why do we even need to abort a request?
One specific use case I needed an abortable request for was inside a React component. The component fetches some data at mount time and sets the fetched data inside the component's internal state.
Because the fetch is an asynchronous operation, the component could be unmounted before the fetch request is resolved. So, if you are working with a useEffect
hook inside a React component, you have to provide a cleanup function that aborts the request.
How to abort a fetch request
Create an AbortController
alongside with your fetch request and pass its signal property in the fetch options:
const { signal } = new AbortController();
const response = await fetch('https://yesno.wtf/api', {signal});
const data = await response.json();
// do something with data
In your cleanup function, you can then call the abort function via signal.abort();
.
Wrapping it up
For my project, I wrapped it all up in a fetch wrapper class. In my project, I am using TypeScript and also took some decisions for my specific use case:
As only json data was needed, I hardcoded response.json()
into it πββοΈ. Also I throw an exception if the response is anything else than 2xx okayish:
/**
* Exceptions from the API
*/
export interface ApiException {
status: number;
details: any;
}
/**
* Request State
*/
export enum RequestState {
IDLE = 'idle',
ABORTED = 'aborted',
PENDING = 'pending',
READY = 'ready',
ERROR = 'error'
}
/**
* Ajax class
*
* Wrapper class around the fetch API.
* It creates an AbortController alongside with the request.
* Also, it keeps track of the request state and throws an ApiException on HTTP status code !== 2xx
*
*/
export class Ajax<T = any> {
promise: Promise<Response> | null;
abortController: AbortController | null;
info: RequestInfo;
init: RequestInit;
state: RequestState;
/**
* Ajax constructor. Takes the same arguments as fetch()
* @param info
* @param init
*/
constructor(info: RequestInfo, init?: RequestInit) {
this.abortController = new AbortController();
this.init = { ...(init || {}), signal: this.abortController.signal };
this.info = info;
this.state = RequestState.IDLE;
this.promise = null;
}
/**
* Send API request.
*
* @returns {any} json data (await (await fetch()).json())
* @throws {ApiException} exception if http response status code is not 2xx
*
*/
async send(): Promise<T> {
this.state = RequestState.PENDING;
try {
this.promise = fetch(this.info, this.init);
const response = await this.promise;
const json = await response.json();
if (! response.ok) {
throw {status: response.status, details: json} as ApiException;
}
this.state = RequestState.READY;
return json;
} catch (ex) {
this.state = RequestState.ERROR;
throw ex;
} finally {
this.abortController = null;
}
}
/**
* Cancel the request.
*/
abort(): void {
if (this.abortController) {
this.state = RequestState.ABORTED;
this.abortController.abort();
this.abortController = null;
}
}
}
Usage:
const request = new Ajax('https://yesno.wtf/api');
const data = await request.send();
// abort it via:
request.abort();
Not sure if it really makes life easier, but it worked for me πββοΈ
I'd love to hear feedback about my solution and how to maybe simplify this. Also, I should have a look into all these http request libraries out there. If you have any recommendations, let me know in the comments.
Top comments (6)
Comparing between
fetch
andXMLHttpRequest
for extra information:Fetch
XHR
I try
abort
in my project:Get random posts from Dev (Indonesian language)
Zen γ» Feb 16 γ» 2 min read
But failed. I use
abort
like this:Great article! Learnt something new today. A great use case for making API calls while changing filters on the frontend.
Finally, cancelling a network request is not one of the differences between RxJS observables and the Fetch API.
I actually learnt something here! Thanks!
I have learnt about this yesterday xD it was searching about giving a timeout to the fetch. I have not tried it yet, I started using axios instead of fetch. Thank you for the post!
Didn't know about the AbortController object. Thanks a lot for this article !