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

Unable to launch React Native project

Error: Module Not Found Cannot find module 'C:\Users\Admin\AppData\Local\npm-cache\_npx\7930a8670f922cdb\node_modules\@babel\parser\lib\index.js'. Please make sure that your package.jso ...

Is there a way to keep a div element anchored at the top of the page until I reach the bottom and then have it stick to the bottom as I continue

I have a question that I hope is clear enough! On my webpage, there is a div at the top that I want to move to the bottom when I scroll and reach the end of the page. Hopefully, you understand what I'm trying to achieve! If anyone has any ideas, I&ap ...

Enhance user experience with Angular Material and TypeScript by implementing an auto-complete feature that allows

Currently facing an issue with my code where creating a new chip triggers the label model to generate a name and ID. The problem arises when trying to select an option from the dropdown menu. Instead of returning the label name, it returns an Object. The ...

Does it typically occur to experience a brief pause following the execution of .innerHTML = xmlhttp.responseText;?

Is it common to experience a brief delay after setting the innerHTML with xmlhttp.responseText? Approximately 1 second delay occurs after xmlhttp.readyState reaches 4. This issue is observed when using Firefox 3.0.10. ...

Guide on integrating buefy (a vue.js component library) into your Laravel blade template

I'm currently integrating buefy into my project, but I'm encountering issues with using vue.js on Laravel 5.8. Can anyone offer assistance? Here is the code snippet from my app.js: require('./bootstrap'); window.Vue = require('v ...

One method to include file upload path along with regular data when sending to controller using jQuery

I am facing an issue while trying to send both a file upload image path and normal data to the controller using jQuery through an Ajax call. Sending only normal data or only the image path separately works fine, but when combining them in one request, it d ...

Transfer the cropped image to the database through AJAX on the client side and PHP on the server side

I am attempting to upload an image to a Database using JavaScript on the client-side and PHP on the server-side. The first step is selecting an image from the gallery. After zooming and cropping the image, it should be passed to the database. The issue ...

JavaScript Class Reference Error

Questioning the mysterious reference error in the JS class from MDN page. The structure of the Bad class's constructor leaves me baffled – is it because the empty constructor calls super() as a default? class Base {} class Good extends Base {} cla ...

Can a C# MVC List<int> be transformed into a JavaScript array?

Can a MVC C# List be converted to a JavaScript array? var jsArray = @Model.IntList; I would really appreciate any assistance on this matter. ...

Add up the duplicate elements in two arrays

I have dynamically created two arrays with the same number of cells (where array.length is the same, representing a key and value association). Below are the arrays: barData.labels["Food", "Food", "Food", "Food", "Food", "Food", "Food", "Food", "Food", "F ...

What is the best way to make this eerie javascript script communicate with my webpage or another jquery file?

I've been struggling with a javascript code that enables infinite scrolling in Tumblr. It seems outdated compared to jQuery, which I'm more familiar with. I've tried integrating this script with my jQuery functions and identifying DOM eleme ...

ReactJS tables that can be filtered and sorted

Within my component's state, there exists an array named 'contributors'. Each element within this array is an object containing various properties. My goal is to present this data in a table format, with the added functionality of sorting an ...

The scrolling action triggered by el.scrollIntoViewIfNeeded() goes way past the top boundary

el.scrollIntoViewIfNeeded() function is used to scroll to element el if it's not within the visible browser area. Although it generally works well, I have encountered some issues when trying to use it with a fixed header. I have provided an example s ...

Utilize regular expressions in TamperMonkey to extract specific groups of text

I'm currently working on a TamperMonkey userscript that aims to identify URLs matching a specific pattern, visit these pages, extract relevant information, and then update the link for each URL with the extracted text. I'm facing some challenges ...

Addressing component validation conflicts in Vuelidate on VUE 3

I am currently experiencing an issue with VUE 3 Vuelidate. In my project, I have 2 components that each use Vuelidate for validation (specifically a list with CRUD functionality implemented using modals). However, when I navigate from one component to anot ...

What is the best way to retrieve the second to last element in a list

When using Protractor, you have convenient methods like .first() and .last() on the ElementArrayFinder: var elements = element.all(by.css(".myclass")); elements.last(); elements.first(); But what about retrieving the element that comes right before the ...

How can I ensure that the size of the Dropdown Menu Item matches its parent in both Bootstrap and CSS styles?

I am working on a navigation bar that has a dropdown menu which appears on hover. I want the size of the dropdown menu items to match the size of the parent element. Here is an image for reference: https://i.stack.imgur.com/oNGmZ.png Currently, the "One ...

Using Node.js and Less to dynamically select a stylesheet source depending on the subdomain

Currently, my tech stack consists of nodejs, express, jade, and less. I have set up routing to different subdomains (for example: college1.domain.com, college2.domain.com). Each college has its own unique stylesheet. I am looking for a way to selectively ...

Every time I attempt to send a PUT request using AJAX, I encounter an error

After developing HTML and AJAX code to update a camera password within my network, I encountered an issue upon form submission. "error" (net::ERR_EMPTY_RESPONSE) Surprisingly, the PUT request functions perfectly when tested with POSTMAN, showing a stat ...

Tips for increasing the height of a popover when clicked

When a user focuses on the password input, a popover displays information. At the bottom of the popover, there is a link. How can I make the popover expand when the user clicks on this link? I have tried adding an !important class for the height value, us ...