Wang's blog Wang's blog
首页
  • 前端文章

    • HTML教程
    • CSS
    • JavaScript
  • 前端框架

    • Vue
    • React
    • VuePress
    • Electron
  • 后端技术

    • Npm
    • Node
    • TypeScript
  • 编程规范

    • 规范
  • 我的笔记
  • Git
  • GitHub
  • VSCode
  • Mac工具
  • 数据库
  • Google
  • 服务器
  • Python爬虫
  • 前端教程
更多
收藏
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Wang Mings

跟随大神,成为大神!
首页
  • 前端文章

    • HTML教程
    • CSS
    • JavaScript
  • 前端框架

    • Vue
    • React
    • VuePress
    • Electron
  • 后端技术

    • Npm
    • Node
    • TypeScript
  • 编程规范

    • 规范
  • 我的笔记
  • Git
  • GitHub
  • VSCode
  • Mac工具
  • 数据库
  • Google
  • 服务器
  • Python爬虫
  • 前端教程
更多
收藏
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • CSS

  • Npm

  • Vue

  • HTML

  • Node

  • Yaml

  • React

  • 框架

  • 规范

  • Electron

  • JS演示

  • VuePress

  • JavaScript

    • DOM
    • 事件
    • 实用API
    • 作用域
    • 基础篇
    • 定时器
    • 内存管理
    • 异步操作
    • 日期处理
    • 点击约束
    • 防抖节流
    • this的学习
    • ES面向对象
    • JS事件详解
    • new命令原理
    • 内置对象篇
    • 函数柯里化
    • 正则表达式
    • 浏览器模型
    • 事件订阅发布
    • 获取页面高度
    • 面向对象编程
    • JS随机打乱数组
    • js各种开发技巧
    • 函数的深入浅出
    • 防抖与节流函数
    • 模块化的使用总结
    • JS获取和修改url参数
    • JS设计模式总结笔记
    • fetch拦截和封装使用
      • 浏览器渲染原理流程
      • JavaScript对象从浅入深
      • JavaScript高级程序设计
      • Promise使用和实现方法
      • JS的种打断点调试方式
      • js深浅拷贝详解与实现
      • 九种跨域方式实现原理
      • 多种数组去重性能对比
      • 闭包原理以及使用场景
      • 判断是否为移动端浏览器
      • 比typeof运算符更准确的类型判断
      • 搞清事件循环、宏任务、微任务
      • 将一维数组按指定长度转为二维数组
    • TypeScript

    • 微信小程序

    • TypeScript-axios

    • 前端
    • JavaScript
    wangmings
    2022-07-19
    目录

    fetch拦截和封装使用

    # fetch拦截和封装使用

    # 前言

    • 我们要做的拦截是什么意思呢? 就是我们可以拦截页面上所有的fetch请求,我们可以在请求之前拦截请求头的信息,和在请求后拦截得到服务器响应的结果。
    • 我们拦截到的内容,可以经过我们处理,想干嘛就干嘛。
    • 拦截器用法参考axios的拦截器用法
    • 最后我们也封装一下统一使用,在真实项目上的操作的封装
    • 你需要掌握的知识点
      • fetch的使用 Fetch API 教程 (opens new window)
      • promise的用法 promise 教程 (opens new window)

    # 实现原理

    • 通过重写fetch方法,添加我们想要的功能,然后替换window下的fetch

    例如: 下面这行代码就是可以替换原生fetch方法了

    window.fetch = Cfetch
    
    1

    # fetch的拦截器实现

    完整代码如下,代码写注释比较详细,我就不做过多解释了

    /**
     * Cfetch
     * 基于原生fetch封装了拦截器功能,暴露出来的Cfetch跟原生fetch用法一致,只是增加了拦截器功能。拦截器用法参考axios的拦截器用法。
     * 拦截器: interceptors
     */
    
    // 定义用来存储拦截请求和拦截响应结果的处理和错误结果处理的函数集合
    let interceptorsReq = []
    let interceptorsReqError = []
    let interceptorsRes = []
    let interceptorsResError = []
    
    // 复制一份原生fetch的方法,后面我们还是得调用原生的fetch,只是我们在fetch之上做一层封装,添加我们想要的功能
    const OriginFetch = window.fetch
    
    function Cfetch(input, init = {}) {
      // interceptorsReq是拦截请求的拦截处理函数集合
      interceptorsReq.forEach(item => {
        init = item(init)
      })
    
      // 在原生fetch外面封装一个promise,为了在promise里面可以对fetch请求的结果做拦截处理。
      // 同时,保证Cfetch函数返回的结果是个promise对象。
      return new Promise((resolve, reject) => {
        // 发起fetch请求,fetch请求的形参是接收上层函数的形参
        OriginFetch(input, init).then(res => {
          // interceptorsRes是拦截响应结果的拦截处理函数集合
          interceptorsRes.forEach(item => {
            // 拦截器对响应结果做处理,把处理后的结果返回给响应结果。
            res = item(res)
          })
          // 将拦截器处理后的响应结果resolve出去
          resolve(res)
        })
          .catch(err => {
            // interceptorsResError是拦截响应错误结果的拦截处理函数集合
            interceptorsResError.forEach(item => {
            // 拦截器对响应错误结果做处理,把处理后的结果返回给响应结果。
              err = item(err)
            })
            reject(err)
          })
      })
    }
    
    // interceptors拦截器提供request和response两种拦截器功能。
    // 可以通过request和response的use方法来绑定两种拦截器的处理函数。
    // use方法接收两个参数,参数为一个callback函数,callback函数用来作为拦截器的成功处理函数,errorCallback作为错误处理函数
    // request.use方法会把callback放在interceptorsReq中,等待执行。
    // response.use方法会把callback放在interceptorsRes中,等待执行。
    // 拦截器的处理函数callback接收一个参数。
    // request拦截器的callback接收的是请求发起前的config;
    // response拦截器的callback接收的是网络请求的response结果。
    const interceptors = {
      request: {
        use(callback, errorCallback) {
          interceptorsReq.push(callback)
          errorCallback && interceptorsReqError.push(errorCallback)
        }
      },
      response: {
        use(callback, errorCallback) {
          interceptorsRes.push(callback)
          errorCallback && interceptorsResError.push(errorCallback)
        }
      }
    }
    
    // 暴露导出这个对象
    export default {
      Cfetch,
      interceptors
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73

    这里解释下为什么我们的interceptors对象,不写在Cfetch函数上面呢,如Cfetch.interceptors = {}, 因为这样写呢,有些网页他们做了反注入的话,会检查原生的fetch上面是不是多了其他属性,

    所以我就不写在Cfetch函数上面了,这样他们就检查不出来。道高一丈,魔高一尺,哈哈哈,如果对方又做了其他反注入,大家可以发挥想法换一种思路注入

    # fetch 封装使用

    import { Cfetch, interceptors } from './fetch'
    // 这里是我项目使用到的js-cookie库,主要是为了拿到token,你们这里改成你们获取token的方式即可
    import Cookies from 'js-cookie'
    
    /**
     * config 自定义配置项
     * @param withoutCheck 不使用默认的接口状态校验,直接返回 response
     * @param returnOrigin 是否返回整个 response 对象,为 false 只返回 response.data
     * @param showError 全局错误时,是否使用统一的报错方式
     * @param canEmpty 传输参数是否可以为空
     * @param mock 是否使用 mock 服务
     * @param timeout 接口请求超时时间,默认10秒
     */
    let configDefault = {
      showError: true,
      canEmpty: false,
      returnOrigin: false,
      withoutCheck: false,
      mock: false,
      timeout: 10000
    }
    
    // 添加请求拦截器
    interceptors.request.use(config => {
      // 这里是我项目使用到的js-cookie库,主要是为了拿到token,你们这里改成你们获取token的方式即可
      const token = Cookies.get('access_token')
      let configTemp = Object.assign({
        responseType: 'json',
        headers: {
          'Content-Type': 'application/json;charset=utf-8',
          authorization: `Bearer ${token}`
        },
      }, configDefault, config)
      console.log('添加请求拦截器 configTemp ==>', configTemp)
      return configTemp
    })
    
    // 添加响应拦截器
    interceptors.response.use(async response => {
      console.log('拦截器response ==>', response)
      console.log('configDefault', configDefault)
      
      // TODO: 这里是复制一份结果处理,在这里可以做一些操作
      const res = await resultReduction(response.clone())
    
     // HTTP 状态码 2xx 状态入口,data.code 为 200 表示数据正确,无任何错误
      if (response.status >= 200 && response.status < 300) {
        return response
      } else { // 非 2xx 状态入口
        if (configDefault.withoutCheck) { // 不进行状态状态检测
          return Promise.reject(response)
        }
        return Promise.reject(response)
      }
    })
    
    // 结果处理,fetch请求响应结果是promise,还得处理
    async function resultReduction(response) {
      let res = ''
      switch (configDefault.responseType) {
        case 'json':
          res = await response.json()
          break
        case 'text':
          res = await response.text()
          break
        case 'blod':
          res = await response.blod()
          break
        default:
          res = await response.json()
          break
      }
      console.log('结果处理', res)
      return res
    }
    
    function request(method, path, data, config) {
      let myInit = {
        method,
        ...configDefault,
        ...config,
        body: JSON.stringify(data),
      }
      if (method === 'GET') {
        let params = ''
        if (data) {
        // 对象转url参数
          params = JSON.stringify(data).replace(/:/g, '=')
            .replace(/"/g, '')
            .replace(/,/g, '&')
            .match(/\{([^)]*)\}/)[1]
        }
        return Cfetch(`${path}?${params}`, {
            ...configDefault,
            ...config,
        })
      }
    
      return Cfetch(path, myInit)
    }
    
    // get请求方法使用封装
    function get(path, data, config) {
      return request('GET', path, data, config)
    }
    
    // post请求方法使用封装
    function post(path, data, config) {
      return request('POST', path, data, config)
    }
    
    // put请求方法使用封装
    function put(path, data, config) {
      return request('PUT', path, data, config)
    }
    
    // delete请求方法使用封装
    function del(path, data, config) {
      return request('DELETE', path, data, config)
    }
    
    export default {
      fetch: Cfetch,
      get,
      post,
      put,
      delete: del
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130

    # 注入页面,拦截页面的fetch请求

    注入js代码,修改fetch方法,达到拦截器的作用,原理很简单, 就是我们通过重写fetch的方法,给fetch添加拦截器的功能,然后替换原生windoe下的fetch即可

    window.fetch = Cfetch
    
    1

    # 最后调用

    import fetchApi from './fetchApi'
    
    winodw.fetch = fetchApi.fetch
    
    fetchApi.get('http://www.xxx', {id: '1'})
    
    fetchApi.post('http://www.xxx', {id: '1'})
    
    1
    2
    3
    4
    5
    6
    7

    成功结果打印如图:

    复制代码,开始你们的调试对接吧。有什么问题,欢迎留言交流

    参考文章:

    基于原生fetch封装一个带有拦截器功能的fetch,类似axios的拦截器 (opens new window)

    Fetch API 教程 (opens new window)

    编辑 (opens new window)
    JS设计模式总结笔记
    浏览器渲染原理流程

    ← JS设计模式总结笔记 浏览器渲染原理流程→

    最近更新
    01
    theme-vdoing-blog博客静态编译问题
    09-16
    02
    搜索引擎
    07-19
    03
    友情链接
    07-19
    更多文章>
    Theme by Vdoing | Copyright © 2019-2022 Evan Xu | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式