59

I want to intercept fetch API requests and responses in JavaScript.

For example, before sending the request I want to intercept the request URL. I'd like to intercept the response once it arrives as well.

The below code is for intercepting responses of all XMLHTTPRequests.

(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);

I want to implement the same feature for fetch() API. How can I do this?

ggorlen
  • 44,755
  • 7
  • 76
  • 106
Hariharan Subramanian
  • 1,360
  • 2
  • 12
  • 22
  • Sounds like you want hack into the Window.Request interface https://fetch.spec.whatwg.org/#request https://developer.mozilla.org/en-US/docs/Web/API/Request to do something similar to what you did in the code example in the question. I personally can’t offer any more specific guidance then saying that’s where you’d probably want to start experimenting – sideshowbarker Aug 01 '17 at 03:33
  • Is there any way to detect all success callback of fetch API call?. For ex: $(document).ajaxSuccess(function( event, xhr, settings ) { }); – Hariharan Subramanian Aug 01 '17 at 14:14
  • The only means you have for checking the response status is to check the `ok` attribute of the response object https://developer.mozilla.org/en-US/docs/Web/API/Response/ok: `fetch(someURL).then(function(response) { if(response.ok) { /* do something */}` – sideshowbarker Aug 01 '17 at 14:24
  • 1
    Thank you @sidehowbarker.. I want to add the success call back for all fetch request in the site. I am going to run my code top of the application.I don't know how many fetch request register in the application based on the request URL in the call back i need to execute some functionality. – Hariharan Subramanian Aug 01 '17 at 21:43

5 Answers5

69

Existing answers show the general structure for mocking fetch in the browser but omit important details.

The accepted answer shows the general pattern for replacing the window.fetch function with custom implementation that intercepts the call and forwards the arguments to fetch. However, the pattern shown doesn't let the interception function do anything with the response (for example, read the status or body or inject a mock) so is only useful for logging request parameters. This is a pretty narrow use case.

This answer uses an async function to let the interceptor await on the fetch promise and presumably work with the response (mocking, reading, etc) but (at the time of writing) has a superfluous closure and doesn't show how to read the response body non-destructively. It also contains a variable aliasing bug leading to a stack overflow.

This answer is the most complete so far but has some irrelevant noise in the callback and doesn't mention anything about cloning the response to enable the body to be collected by the interceptor. It doesn't illustrate how a mock could be returned.

Here's a minimal, complete example that rectifies these issues, showing how to handle parameter logging, reading the body without harming the original caller by cloning the response and (optionally) providing a mock response.

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));
ggorlen
  • 44,755
  • 7
  • 76
  • 106
46

For intercepting the fetch request and parameter we can go for below mentioned way. its resolved my issue.

 const constantMock = window.fetch;
 window.fetch = function() {
     // Get the parameter in arguments
     // Intercept the parameter here 
    return constantMock.apply(this, arguments)
 }
ctf0
  • 6,991
  • 5
  • 37
  • 46
Hariharan Subramanian
  • 1,360
  • 2
  • 12
  • 22
  • How to use this code. do we need to call constantMock after this code? – Sunny Nov 10 '21 at 06:38
  • just call the fetch API a regular way. we already defined our own fetch so the call will be intercepted in our function definition. – Hariharan Subramanian Nov 11 '21 at 17:41
  • is it fetch all browser API? elaborate my application loaded with SSO(OKTA), wanna change one api response, is it possible? – nisar Nov 16 '21 at 13:30
  • yes. it will intercept all the fetch API. but you can have condition check inside the function based on you can do the manipulation. just console log the arguments and this object, you can see all the values – Hariharan Subramanian Nov 16 '21 at 15:33
24

For intercepting the response body you need to create a new Promise and resolve or reject current into "then" code. It solved for me and keep content for real app, e.g. React, etc.

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

  return new Promise((resolve, reject) => {
    constantMock
      .apply(this, arguments)
      .then((response) => {
        if (response.url.indexOf("/me") > -1 &&
            response.type != "cors") {
          console.log(response);
          // do something for specificconditions
        }
        resolve(response);
      })
      .catch((error) => {
        reject(error);
      })
  });
}
Kong
  • 8,792
  • 15
  • 68
  • 98
Eduardo Fabricio
  • 2,151
  • 2
  • 25
  • 32
  • 20
    Just want to add a detail: if you need the response body to "do something for specific conditions", do not forget to clone the response, otherwise, the final user of the promise will get "TypeError: Body has already been consumed". So, do like "response.clone().json()" or "response.clone().text()" to get the body. – Michael Mar 03 '19 at 13:29
18
const fetch = window.fetch;
window.fetch = (...args) => (async(args) => {
    var result = await fetch(...args);
    console.log(result); // intercept response here
    return result;
})(args);
AshUK
  • 1,225
  • 14
  • 9
  • 6
    This blows the stack in Chrome 83.0. `const origFetch = window.fetch` and `await origFetch(...args)` fixes the problem. Also, I'm not sure why the outermost function exists. You can just use `fetch = async (...args) => ...` and skip the IIFE. – ggorlen Nov 22 '20 at 15:31
1

Further to Hariharan's answer, Here is how I updated spinner state in Redux before and after each fetch request

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

// Set up interceptor on all fetch API calls
// Increments redux spinner state when api is called
// Decrements redux spinner state again when it is returned
(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;
            })
    }
})();
Neil Shah
  • 173
  • 8