Monitor fetch() API calls and responses in JavaScript

I’m looking to intercept fetch API requests and responses using JavaScript.

Specifically, I want to be able to capture the request URL before it is sent and also intercept the response once it has been received.

The code below demonstrates how to intercept responses for all instances of XMLHTTPRequest.

(function(open) {
  XMLHttpRequest.prototype.open = function(XMLHttpRequest) {
    var self = this;
    this.addEventListener("readystatechange", function() {
      if (this.responseText.length > 0 &&
          this.readyState == 4 &&
          this.responseURL.indexOf('www.google.com') >= 0) {

        Object.defineProperty(self, 'response', {
          get: function() { return bValue; },
          set: function(newValue) { bValue = newValue; },
          enumerable: true,
          configurable: true
        });
        self.response = 'updated value' // Intercepted Value 
      }
    }, false);
    open.apply(this, arguments);
  };
})(XMLHttpRequest.prototype.open);

Now, I am interested in implementing a similar feature for the fetch() API. How can I achieve this?

Answer №1

Previous solutions have outlined the basic structure for mocking fetch in the browser, but some key details have been left out.

The main answer provides a general approach to replacing the window.fetch function with a custom implementation that intercepts the call and forwards the arguments to fetch. However, this method does not allow the interception function to manipulate the response (e.g., access status or body contents), making it limited to just logging request parameters.

Another solution introduces an async function to enable the interceptor to use await on the fetch promise and potentially interact with the response data. But there are issues such as unnecessary closures, lack of guidance on reading the response body without altering it, and a variable aliasing bug causing a stack overflow.

This alternative answer is more comprehensive but includes unrelated content in the callback and overlooks crucial information on cloning the response to safely collect the body using the interceptor. It also fails to demonstrate how a mock response could be implemented.

Below is a refined example addressing these concerns, showcasing parameter logging, non-destructive body retrieval through response cloning (using clone()), and the option to provide a mock response if needed.

const {fetch: origFetch} = window;
window.fetch = async (...args) => {
  console.log("fetch called with args:", args);
  const response = await origFetch(...args);

  /* work with the cloned response in a separate promise
     chain -- could use the same chain with `await`. */
  response
    .clone()
    .json()
    .then(data => console.log("intercepted response data:", data))
    .catch(err => console.error(err));

  /* the original response can be resolved unmodified: */
  //return response;

  /* or mock the response: */
  return new Response(JSON.stringify({
    userId: 1,
    id: 1,
    title: "Mocked!!",
    completed: false
  }));
};

// test it out with a typical fetch call
fetch("https://jsonplaceholder.typicode.com/todos/1")
  .then(response => response.json())
  .then(data => console.log("original caller received:", data))
  .catch(err => console.error(err));

Answer №2

If you need to intercept the fetch request and parameters, one way to do it is shown below. This method worked for me in resolving my issue.

 const constantMock = window.fetch;
 window.fetch = function() {
     // Extract the parameters from the arguments
     // Intercept the parameters here 
    return constantMock.apply(this, arguments)
 }

Answer №3

To capture the response body, you must create a new Promise and handle it within the "then" code block. This approach worked for me and is suitable for actual applications like React.

const originalFetch = window.fetch;
window.fetch = function() {
  console.log(arguments);

  return new Promise((resolve, reject) => {
    originalFetch
      .apply(this, arguments)
      .then((response) => {
        if (response.url.indexOf("/me") > -1 && response.type !== "cors") {
          console.log(response);
          // Perform specific actions based on conditions
        }
        resolve(response);
      })
      .catch((error) => {
        reject(error);
      })
  });
}

Answer №4

let apiFetch = window.fetch;
window.fetch = (...args) => (async(args) => {
    var response = await apiFetch(...args);
    console.log(response); // handle response here
    return response;
})(args);

Answer №5

In addition to Hariharan's solution, I implemented a method to manage spinner state in Redux before and after each API call.

import store from './../store';

// Implement an interceptor for all fetch API requests
// Increases the redux spinner state when an API call is made
// Decreases the redux spinner state once the API call is resolved
(function() {
    const originalFetch = window.fetch;
    window.fetch = function() {
        store.dispatch({type: 'show-spinner'})
        return originalFetch.apply(this, arguments)
            .then((res) => {
                store.dispatch({type: 'hide-spinner'})
                return res;
            })
    }
})();

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Finding the text within a textarea using jQuery

My journey with jQuery has just begun, and following a few tutorials has made me feel somewhat proficient in using it. I had this cool idea to create a 'console' on my webpage where users can press the ` key (similar to FPS games) to send Ajax re ...

An unexpected error occurred while attempting to retrieve data from Firebase and display it in another component

An error occurred: Unhandled Runtime Error Error: Element type is invalid - expected a string (for built-in components) or a class/function (for composite components) but got undefined. This could be due to forgetting to export your component from the defi ...

Using $regex in mongodb's pipeline operations allows for advanced string

I need to be able to use $regex in order to search for any words that contain a specific keyword provided by the user, but I'm experiencing issues. Here's what I have so far: aggregatePipeline.push({ $match: { name: { $reg ...

Ways to implement a filter pipe on a property within an array of objects with an unspecified value

Currently, I'm tackling a project in Angular 8 and my data consists of an array of objects with various values: let studentArray = [ { Name: 'Anu', Mark: 50, IsPassed: true }, { Name: 'Raj', Mark: 20, IsPassed: false }, { Na ...

The React hamburger menu triggers a re-render of child elements within the layout

I am currently working with React and Material UI v5. Within my layout, I have a menu component with children. Whenever I click on the menu, it triggers a refresh of the children components. I attempted to resolve this by encapsulating the child components ...

The development server fails to respond when initializing a new project following the NextJs documentation for an empty project

After consulting the NextJs framework documentation, I meticulously followed the setup instructions to initialize an empty project : mkdir hello-next cd hello-next npm init -y npm install --save react react-dom next mkdir pages Subsequently, I included t ...

What could be causing React to throw an error?

Check out this React Component: GeneralInformation = React.createClass({ totalCaptialRaisedPanel: function() { var offeringInfo = this.props.company.data.offerings.data[0]; var percentageComplete = (offeringInfo.capital_raised / offer ...

I possess an array containing objects of different lengths depending on the chosen city. How can I pinpoint the element that contains an object with a specific property?

My dilemma arises from the fact that the length of an array depends on the selected city, making it impossible to select elements using an index. In this scenario, I need to devise a method to choose elements based on the value of one of their properties. ...

In ReactJs, what is the best way to load a new component when a button is clicked?

In ReactJs, I have developed a main component known as MainPage using Material-UI. import React from 'react'; import Grid from '@material-ui/core/Grid'; import Button from '@material-ui/core/Button'; import CssBaseline from & ...

Once the "Get Route" button is pressed, I want to save my JavaScript variable into a database

I am seeking to automatically extract data from the Google Maps API and store it in my MySQL database. Specifically, I want details such as source address, destination address, distance, and duration for all available routes to be inserted into my database ...

Arrangement featuring two distinct types of tiles

Looking for a way to achieve this layout using just CSS or a combination of CSS and a little JS. Click on my avatar to see the reference image enlarged =) I've tried using floats and display options but haven't been successful. Take a look at htt ...

Is it possible to implement marker dragging in Google Maps v3 using JavaScript? How can this be achieved?

I am currently using this code to search for an address, drop a marker, and drag it afterwards. <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8"/> <title&g ...

Storing a Promise in a Variable in React

As a newcomer to JavaScript/React, I am finding it challenging to grasp the concept of using Promise and async. To illustrate, consider the API call getSimById in a JS file that returns a Promise: export function getSimById(simId) { return fetch(simsUrl ...

Issues with my transpiled and typed TypeScript npm module: How can I effectively use it in a TypeScript project?

I'm trying to experiment with TypeScript. Recently, I created a simple "hello world" TypeScript module and shared it on npm. It's very basic, just has a default export: export default function hello(target: string = 'World'): void { ...

What is the best way to distribute a function within a div container?

I'm currently working on a function that manages the show/hide functionality and position of tooltips: tooltip = (e) => { // show/hide and position of tooltip // retrieve element data } In addition, I have div elements whe ...

Utilizing adapter headers in contexts other than ActiveModelAdapter

I have successfully implemented my authorization system with Ember Data. All my ember-data calls are secure and signed correctly using adapter.ajax() instead of $.ajax. However, I am facing a situation where I need to utilize a third-party upload library t ...

Error-free ngClick doesn't trigger AngularJS controller method

I am struggling to trigger the removePlayer(playerId) method upon clicking a button. However, it seems that the method is not being called, as I have placed a console.log() statement at the top and the console remains empty. This situation has left me puz ...

Saving MongoDB query results to a file using the built-in Node.js driver

I have been attempting to save the output of a MongoDB query to a file using the native Node.js driver. Below is my code (which I found on this post: Writing files in Node.js): var query = require('./queries.js'); var fs = require('fs' ...

Issue with React component not displaying in the browser.Here are some

I am currently following a React tutorial on and I'm facing an issue where the Counter component is not displaying on the page. The generated HTML looks like this: <html> <head> <script src="/bundle.js" ></script> </he ...

Looking to change the input field names by increasing or decreasing when clicking on a div using jQuery?

As a beginner in the world of jQuery, I am working on mastering some basic concepts. Currently, my goal is to create auto incrementing/decrementing input field names within a 'div' when clicking on an add/remove button. Below is the HTML code I a ...