在 JavaScript 中拦截 fetch() API 请求和响应

Posted

技术标签:

【中文标题】在 JavaScript 中拦截 fetch() API 请求和响应【英文标题】:Intercept fetch() API requests and responses in JavaScript 【发布时间】:2018-01-07 13:46:10 【问题描述】:

我想在 javascript 中拦截 fetch API 请求和响应。

例如,在发送请求之前我要截取请求的 URL。我也想在响应到达后拦截它。

以下代码用于拦截所有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);

我想为fetch() API 实现相同的功能。我该怎么做?

【问题讨论】:

听起来你想侵入 Window.Request 接口 fetch.spec.whatwg.org/#request developer.mozilla.org/en-US/docs/Web/API/Request 来做类似于你在问题代码示例中所做的事情。我个人不能提供任何更具体的指导,然后说这就是你可能想要开始试验的地方 有什么方法可以检测到 fetch API 调用的所有成功回调吗?例如:$(document).ajaxSuccess(function(event, xhr, settings) ); 检查响应状态的唯一方法是检查响应对象developer.mozilla.org/en-US/docs/Web/API/Response/ok的ok属性:fetch(someURL).then(function(response) if(response.ok) /* do something */ 谢谢@sidehowbarker。我想为站点中的所有获取请求添加成功回调。我将在应用程序的顶部运行我的代码。我不知道根据回调中的请求 URL 在应用程序中注册了多少获取请求,我需要执行一些功能。 【参考方案1】:

现有答案显示了在浏览器中模拟 fetch 的一般结构,但省略了重要细节。

accepted answer 显示了用自定义实现替换window.fetch 函数的一般模式,该实现拦截调用并将参数转发给fetch。但是,显示的模式不允许拦截函数对响应做任何事情(例如,读取状态或正文或注入模拟),因此仅对记录请求参数有用。这是一个非常狭窄的用例。

This answer 使用async 函数让fetch 上的拦截器await 承诺并可能处理响应(模拟、读取等),但(在撰写本文时)有一个多余的闭包并且没有展示如何非破坏性地读取响应正文。它还包含导致堆栈溢出的变量别名错误。

This answer 是迄今为止最完整的,但在回调中有一些不相关的噪音,并且没有提及克隆响应以使拦截器收集主体的任何内容。它没有说明如何返回模拟。

这是一个纠正这些问题的最小、完整示例,展示了如何处理参数记录、在不损害原始调用者 by cloning the 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))
;

【讨论】:

这是稳定性的最佳答案【参考方案2】:

对于 Hariharan 的回答,以下是我在每次获取请求之前和之后在 Redux 中更新微调器状态的方式

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

【讨论】:

【参考方案3】:
const fetch = window.fetch;
window.fetch = (...args) => (async(args) => 
    var result = await fetch(...args);
    console.log(result); // intercept response here
    return result;
)(args);

【讨论】:

这会破坏 Chrome 83.0 中的堆栈。 const origFetch = window.fetchawait origFetch(...args) 解决了这个问题。另外,我不确定为什么存在最外层的函数。您可以使用fetch = async (...args) => ... 并跳过 IIFE。【参考方案4】:

为了拦截响应正文,您需要创建一个新的 Promise 并将当前解析或拒绝为“then”代码。它为我解决了问题并保留了真实应用程序的内容。例如。反应等等。

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

【讨论】:

只是想补充一个细节:如果你需要响应体“针对特定条件做某事”,不要忘记克隆响应,否则,promise的最终用户会得到“TypeError :身体已经被消耗”。因此,请像“response.clone().json()”或“response.clone().text()”一样获取正文。【参考方案5】:

为了拦截获取请求和参数,我们可以采用下面提到的方式。它解决了我的问题。

 const constantMock = window.fetch;
 window.fetch = function() 
     // Get the parameter in arguments
     // Intercept the parameter here 
    return constantMock.apply(this, arguments)
 

【讨论】:

如何使用此代码。我们需要在这段代码之后调用 constantMock 吗? 只需以常规方式调用 fetch API。我们已经定义了自己的 fetch,所以调用将在我们的函数定义中被拦截。 它获取所有浏览器 API 吗?详细说明我加载了 SSO(OKTA) 的应用程序,想更改一个 api 响应,可以吗? 是的。它会拦截所有的 fetch API。但是您可以根据您可以进行的操作在函数内部进行条件检查。只需控制台记录参数和这个对象,你就可以看到所有的值

以上是关于在 JavaScript 中拦截 fetch() API 请求和响应的主要内容,如果未能解决你的问题,请参考以下文章

fetch拦截器的实现

可以将 Fetch API 用作请求拦截器吗?

开源一款好用的AJAX和FETCH拦截器

在 javascript 中的另一个 fetch 中使用 fetch

在 javascript 中使用 fetch 请求 API 存在 CORS 策略错误

fetch请求数据