Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Intercept fetch() API requests and responses in JavaScript

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?

like image 746
Hariharan Subramanian Avatar asked Jul 31 '17 20:07

Hariharan Subramanian


People also ask

How do you intercept request in JavaScript?

To intercept HTTP requests, use the webRequest API. This API enables you to add listeners for various stages of making an HTTP request. In the listeners, you can: Get access to request headers and bodies and response headers.

What is intercept in JS?

Interceptors are code blocks that you can use to preprocess or post-process HTTP calls, helping with global error handling, authentication, logging, and more. In this article, you'll learn how to intercept JavaScript Fetch API calls.

What is fetch API in JavaScript?

The Fetch API provides a JavaScript interface for accessing and manipulating parts of the HTTP pipeline, such as requests and responses. It also provides a global fetch() method that provides an easy, logical way to fetch resources asynchronously across the network.

What does the fetch () method return?

The fetch() method returns a promise that can either be resolved or not. Thus, this method always returns a value and the only possible case where it may fail to return anything is a server error.


5 Answers

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(body => console.log("intercepted response:", body))
    .catch(err => console.error(err))
  ;
    
  /* the original response can be resolved unmodified: */
  //return response;
  
  /* or mock the response: */
  return {
    ok: true,
    status: 200,
    json: async () => ({
      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(json => console.log("original caller received:", json))
  .catch(err => console.error(err))
;
like image 121
ggorlen Avatar answered Oct 02 '22 13:10

ggorlen


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)
 }
like image 23
Hariharan Subramanian Avatar answered Sep 30 '22 13:09

Hariharan Subramanian


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(response);
      })
  });
}
like image 36
Eduardo Fabricio Avatar answered Oct 01 '22 13:10

Eduardo Fabricio


const fetch = window.fetch;
window.fetch = (...args) => (async(args) => {
    var result = await fetch(...args);
    console.log(result); // intercept response here
    return result;
})(args);
like image 41
AshUK Avatar answered Oct 02 '22 13:10

AshUK


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;
            })
    }
})();
like image 36
Neil Shah Avatar answered Oct 01 '22 13:10

Neil Shah