Useful JavaScript helper for creating HTTP requests and processing responses using JavaScript Fetch API.

I will share here a github gist that contains all the necessary code to easly use the Fetch API, a promised-based data fetching in plain JavaScript, to manage HTTP interactions in your frontend app.

1 – .then() or .catch()

Before showing you the code, it should be noted that, despite the fact that the Fetch API is a new powerful and flexible interface for fetching resources (compared to the old APIs like XMLHttpRequest), it comes with some drawbacks or let’s see a particular specification that differs from what we used to do before like with jQuery.ajax() ! The most important for me is :

The Promise returned from fetch() won’t reject on HTTP error status even if the response is an HTTP 404 or 500. Instead, it will resolve normally (with ok status set to false), and it will only reject on network failure or if anything prevented the request from completing.

You can read more following this link Using Fetch. But if we come back to the specification above, i’ll say this could be inconvenient since some errors are resolved as a normal response, in other words, those errors are intercepted in the .then() function or the returned promise and not in the .catch(). In this helper all errors are rejected and so, are available in catch() function.

2 – .json() or .text()

I am also dealing with a somewhat particular case, that of empty responses returned from backend. In this case, if you would to allow this behavior, use .text() function intead of .json() to read the stream-based data of the response, otherwise you’ll get a confusing exception.

3 – Dev-friendly responses

Finally, this helper allow you to get Dev-friendly reponses, I mean you’ll have a same structure for all types of responses with two props headers and body. This is not the case with the Fetch API. On top of that, the responses are typed objects (classes) and you don’t have to do the hard work of checking status when processing a reponse, you can just some thing like that :

if (response instanceof AppResponse)
/*this means there are no errors!*/

if (response instanceof AppError)
/*this means there is an error!*/

/*AppError is base class of all errors classes*/
if (response instanceof NotFoundError)
/*this means that your AppError is a HTTP 404 Not Found error*/

4 – Code

You can see TypeScript version from here http_helper.js, where I used await/async and generics.

export const Status = {
Ok: 200,
Unassigned: 299,
BadRequest: 400,
Unauthorized: 401,
NotFound: 404,
};
export const Methods = {
Get: "GET",
Post: "POST",
Put: "PUT",
Delete: "DELETE",
};
export const Headers = {
Accept: "application/json",
"Content-Type": "application/json",
};
export const AuthorizationType = {
Bearer: "bearer ",
Basic: "basic "
}
export const Credentials = {
SameOrigin: "same-origin", /* default */
Include: "include",
Omit: "omit"
}
export const addAuthorization = (headers, type, value) => {
headers["Authorization"] = `${type}${value}`;
}
class HttpHelper {
static post = (url, body) => {
return fetch(url, {
method: Methods.Post,
/*credentials: Credentials.Include,*/
headers: Headers,
body: JSON.stringify(body),
})
.then(this.handleResponse)
.catch(this.handleUnexpectedError);
};
static get = (url, query) => {
if (query)
url += "?" + new URLSearchParams(query).toString();
return fetch(url, {
method: Methods.Get,
/*credentials: Credentials.Include,*/
headers: Headers
})
.then(this.handleResponse)
.catch(this.handleUnexpectedError);
};
static handleResponse = (response) => {
const headers = this.readHeaders(response.headers);
if (this.isError(response))
return this.handleExpectedError(response, headers);
return response.json().then((body) => {
return new AppResponse(body, headers);
});
/*In case of issue with empty responses use .text() instead of .json()*/
/*Same thing to do for handleExpectedError*/
// return response.text().then((bodyText) => {
// let body = bodyText ? JSON.parse(bodyText) : {};
// return new AppResponse(body, headers);
// });
};
static handleExpectedError = (error, headers) => {
/*Expected (Unhandled exceptions if treated to 500)*/
/*See : https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch*/
return error.json().then((body) => {
switch (error.status) {
case Status.BadRequest:
return Promise.reject(new BadRequestError(body, headers));
case Status.Unauthorized:
return Promise.reject(new UnauthorizedError(body, headers));
case Status.NotFound:
return Promise.reject(new NotFoundError(body, headers));
default:
return Promise.reject(new AppError(body, headers));
}
});
};
static handleUnexpectedError = (error) => {
/*Unexpected: Server offline, Network down, Unhandled exceptions*/
/*return need to be rejected again*/
if (!(error instanceof AppError))
return Promise.reject(new AppError(error));
/*handleExpectedError return will get here*/
return Promise.reject(error);
};
static isError = (response) => {
return response.status < Status.Ok || response.status > Status.Unassigned;
};
static readHeaders = (headers) => {
let headersObject = {};
headers.forEach((value, key) => {
headersObject[key] = value;
});
return headersObject;
};
}
export default HttpHelper;
export class AppResponse {
constructor(body = null, headers = null) {
this.body = body;
this.headers = headers;
}
}
export class AppError {
constructor(body = null, headers = null) {
this.body = body;
this.headers = headers;
}
}
export class NotFoundError extends AppError {
constructor(body = null, headers = null) {
super(body, headers);
}
}
export class BadRequestError extends AppError {
constructor(body = null, headers = null) {
super(body, headers);
}
}
export class UnauthorizedError extends AppError {
constructor(body = null, headers = null) {
super(body, headers);
}
}
view raw http_helper.js hosted with ❤ by GitHub