如何拦截XMLHttpRequest响应

2,422 阅读2分钟

前言

在公司项目vue开发中,我们用mockjs这个库来模拟后段数据来开发前面页面,后端开发进度慢的情况下,前后端不能联调,mockjs 简直就是我们福音啊。而且 mockjs 能让我们的项目在每发出一个 xhr 请求的情况下,完整模拟整个页面的操作流程。

思考

mockjs其实可以模拟出很多符合场景的数据,但是二进制文件流就不行了,如我们用axios发出一个responseType: blob的请求时,其实是不生效的。但是我们要模拟一个文件数据怎么办?

方法一:node

在本地开发的时候,我们往往是将 mock 的代码放到 webpack(node) 处理的,实际上相当于开启了一个 node 服务,这样的情况下结合 mockjs,可以模拟出任何格式的数据,包括文件(二进制流),模拟二进制流的时候,通常是本地新建一个文件如 PDF 文 件,用 fs node 模块读取,然后返回到前端。有兴趣的可以了解下 node 的fs模块

方法二: xhr重写

终于回归正题。理论上我们实现一个拦截 XMLHttpRequest 响应的方法,就能修改响应值,解决 mockjs 无法模拟文件流的问题了

 // 定义个请求方法
function request(url, parmas = null, method = 'POST') {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open(method, url, true);
    xhr.responseType = 'blob'
    xhr.send(parmas)
    xhr.onreadystatechange = function () {
      const { status, readyState, response } = this;
      console.log('状态', status, readyState)
      const res = {
        data: response,
        status
      }
      if (readyState === 4) {
        if (status === 200) {
          console.log('response', response)
          resolve(res)
        } else {
          reject(res)
        }
      }
    }
  })
}

// 获取本地静态文件
async function getLocalFile(url) {
  const res = await request(url, null, 'GET').catch(() => false);
  if (res !== false) {
    // 成功拿到本地文件
  }
}

getLocalFile('./files/001.jpeg'); // url为项目下一个文件路径

将代码复制到一个 html 里面,可以用 vs code 的live server查看快速开启一个服务来运行 html, 在控制台可以看到文件请求成功了,debugger的地方也可以查看到 response的类型是一个Blob类型

在上面的getLocalFile方法中,实例化了一个XMLHttpRequest对象用法发送一个 xhr 请求。要拦截一个请求,不让它发送到后端,可以重写xhr.send方法, 就可以不让它发送到后端了

 (() => {
   // 设置 xhr 字段为可写方式,不设置的话,是不能直接用 this.response 的方式来改变响应值
   const setWriteKeys = ['status', 'statusText', 'response', 'responseText', 'readyState'];
   const open = XMLHttpRequest.prototype.open;
   const send = XMLHttpRequest.prototype.send;
   XMLHttpRequest.prototype.open = function (...agrs) {
     open.apply(this, agrs)
   };
   XMLHttpRequest.prototype.send = function (...agrs) {
     setWriteKeys.forEach(key => {
       Object.defineProperty(this, key, {
         writable: true
       })
     })
     console.log('send', this)
     setTimeout(() => {
       // 手动改变响应状态和值
       this.status = 200
       this.readyState = 4; // 原生xhr中该值改变,会触发onreadystatechange方法
       this.statusText = 'OK'
       this.response = [1, 2, 3, 4];// 这里可以自定义mock的数据
       // 手动触发会onreadystatechange方法
       this.dispatchEvent(new Event('readystatechange'))
     }, 1000);

     // send.apply(this, agrs) // 这里这方法就不再调用了,如果调用了,xhr还是会发送到后端
   };
 })();
 
 // 接前面代码

总结:拦截 XMLHttpRequest 的主要思路是重写 send 的方法,改变响应值,触发状态改变方法