Skip to content

Working with the Fetch API

To create a reusable fetch wrapper that accepts request options as inputs, we can define a function that abstracts the basic logic of making a fetch request. This wrapper can handle common tasks like error handling, custom headers, or any default configurations, and will allow you to pass in the full set of options as needed.

The wrapper should be implemented as an ES6 module. This means that the code is stored in a file named fetchWrapper.js and can be imported into other files using the import statement.

  • You can use this ES module throughout your application for any type of HTTP request.
  • Error handling is in one place, which makes your code easier to maintain and debug.
  • Set defaults for common parameters like method, headers, and mode, while still allowing customization when needed.
  • JSON responses are parsed automatically, with graceful handling of non-JSON content like plain text or HTML.

Here’s a basic example of such a wrapper implemented as an ES module. The following code must be stored in a file named fetchWrapper.js:

fetchWrapper.js
/**
* A wrapper class for making HTTP requests using the Fetch API.
* Provides a method to send HTTP requests with default and custom options.
*/
export class FetchWrapper {
/**
* Sends an HTTP request using the Fetch API.
*
* @param {string} uri The URI identifying the targeted resource to which the request will be sent.
* @param {Object} [options={}] Optional configuration object for the request.
* @returns {Promise<Object>} A promise that resolves with the parsed JSON response body from the server.
* @throws {Error} Throws an error if the request fails (network errors or non-2xx HTTP status codes).
*
* @example
* const fetchWrapper = new FetchWrapper();
* try {
* const data = await fetchWrapper.sendRequest('https://api.tvmase.com/shows');
* console.log(data);
* } catch (error) {
* console.error('Request failed:', error);
* }
*/
async sendRequest(uri, options = {}) {
//* Default options to apply to every request.
//* NOTE: These options can be overridden.
const defaultOptions = {
method: 'GET',
cache: 'no-cache'
}
//* Merge default options with the supplied custom options.
const requestOptions = {
...defaultOptions,
...options,
headers: {
...defaultOptions.headers,
...options.headers,
}
};
try {
//* Send the fetch request
const response = await fetch(uri, requestOptions);
//* Check if the request was successful (status in the range 200-299)
if (!response.ok) {
// Handle non-success responses (e.g., 404, 500)
//!NOTE: Retrieve any error details from the response body for further processing.
const errorInfo = await response.json();
//TODO: You could implement a separate function for throwing different types exceptions based on the received status code.
//! Throw a custom exception containing the retrieved error details from.
throw new CustomError(`Request failed with status ${response.status}`, response.status, errorInfo);
//throw new Error(`Request failed with status ${response.status} reason: ${response.statusText}`);
}
//* Parse the response body as JSON (assuming JSON content)
//* If the response doesn't contain JSON, this will throw an error.
return await response.json();
} catch (error) {
// Handle network or other errors
console.error('Error during fetch operation:', error);
throw error; // Rethrow or return an error object as per your needs
}
}
}
export class CustomError extends Error {
constructor(message, code, details) {
super(message); // Call the parent constructor with the message
this.name = this.constructor.name; // Set the error name to CustomError
this.code = code; // Custom error code
this.details = details; // Additional details or context
// Optional: Ensure the stack trace is correct for this subclass
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor);
}
}
}
javascript
  • URI and Options: The sendRequest method accepts two parameters:

    • uri: The endpoint you’re sending the request to.
    • options: An object that contains additional options for the request (e.g., method, headers, body, etc.). It is optional and can be provided to customize the behavior of the request.
  • Default Options:

    • The function sets some default values for options like method, headers.
    • The headers object is merged, allowing you to override the default headers, like Content-Type, with your own headers if needed.
  • Error Handling:

    • The function checks if the response is successful (response.ok). If not, it throws an error with the response status.
    • If there’s an issue parsing the response body as JSON (e.g., the server returns HTML or plain text), it throws an error.
    • The try-catch block around the fetch ensures that any network-related errors (e.g., timeout, no connection) are also handled gracefully.

To import the FetchWrapper class in another JavaScript file, you can use the following import statement:

import { FetchWrapper } from './path/to/fetchWrapper';
javascript

Make sure to replace './path/to/fetchWrapper' with the correct relative path to where the FetchWrapper class is defined. For example, if the file is in the same directory, you would write:

import { FetchWrapper } from './fetchWrapper';
javascript

Sending a GET request with default options:

async function fetchData() {
try {
const uri = 'https://api.example.com/data';
const fetchWrapper = new FetchWrapper();
const data = await fetchWrapper.sendRequest(uri);
console.log(data); // Process the response data
} catch (error) {
console.error('Fetch error:', error);
}
}
js

You can pass an options object to the sendRequest method to customize the request. The options object can contain the following properties (as shown in the example below):

  • method: The HTTP method (POST, PUT, etc.) to use for the request.
  • headers: An object containing the headers to be sent with the request (Content-Type, Accept, Authorization, etc.).
  • body: The body of the request. This can be a JSON string, a form data object, or a Blob object.
// Sending a POST request with custom request options including headers and body
async function postData() {
try {
//NOTE: The body of the request is the JSON data that will be sent to the server.
//TODO: You need to use the DOM API to:
//-------------------------------------
// 1) get the data from the form fields.
// 2) Store values you retrieved in a JavaScript object or array of objects, depending on the structure of the data required to be passed as input when your create new resources (POST request).
// 3) Convert the JavaScript object or array to a JSON string using JSON.stringify().
//NOTE: This is a simple example of a JSON object. You need to REPLACE IT.Use the DOM API to get the data from the form fields and store it in a JavaScript object or array of objects.
const person = {
firstName: 'Ladybug',
lastName: 'Red',
age: 25
};
const uri = 'https://api.example.com/submit';
// Request options
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Bearer token123',
},
body: JSON.stringify(person),
}
const fetchWrapper = new FetchWrapper();
const response = await fetchWrapper.sendRequest(uri, options);
console.log(response); // Process the response
} catch (error) {
console.error('Post request error:', error);
}
}
js