Fetch请求时的拦截器,不想用axios以及Umi的request(自带拦截器), 我想自己造,让我装逼一下

231 阅读3分钟

axios本身就封装了拦截器听说,直接可以用他的,如果你想的话

  • 前提,某领导要做大屏,可能想要炫耀吧,单独开一个project,需要拿原先网页的token去请求,涉及token时间过期需要refresh重新刷新获取,令一种状况是过期时间好没到,API返回值就出现401这种类似status, 按常理来说,这种状况一般会重定向到login页面,账号密码重新登录,看是否成功返回data (PS:大屏一般免登录,自己处理)
  • 下面这方法刚好在掘金,看到有个大佬二次封装 Fecth , 白嫖一下我觉得可以

fetch二次封装拦截的实现,参考axios的拦截器用法

// 定义用来存储拦截请求和拦截响应结果的处理和错误结果处理的函数集合 
let interceptorsReq = [] 
let interceptorsReqError = [] 
let interceptorsRes = [] 
let interceptorsResError = []

// 后面我们还是得调用原生的fetch,只是我们在fetch之上做一层封装,添加我们想要的功能
const OriginFetch = window.fetch;

export function Cfetch(input, init = {}) {
  // 是拦截请求的拦截处理函数集合
  interceptorsReq.forEach(item => {
    init = item(init, input)
  })

  // 在原生fetch外面封装一个promise,为了在promise里面可以对fetch请求的结果做拦截处理
  // 同时,保证Cfetch函数返回的结果是个promise对象
  return new Promise((resolve, reject) => {
    OriginFetch(`${API_SERVER}${input}`, init)
      .then(res => {
        // 是拦截响应结果的拦截处理函数集合
        interceptorsRes.forEach(item => {
          // 拦截器对响应结果做处理,把处理后的结果返回给响应结果
          res = item(res)
        })
        // 将拦截器处理后的响应结果resolve出去
        resolve(res)
      })
      .catch(err => {
        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结果

export const interceptors = {
  // 页面引用 "./_fetch.js" 时,一定要use(……)一次,储存
  request: {
    use(callback, errorCallback) {
      interceptorsReq.push(callback);
      errorCallback && interceptorsReqError.push(errorCallback)
    }
  },
  // 页面引用 "./_fetch.js" 时,一定要use(……)一次,储存
  response: {
    use(callback, errorCallback) {
      interceptorsRes.push(callback);
      errorCallback && interceptorsResError.push(errorCallback)
    }
  }
}

这里肯定需要request拦截,我们需要知道当前请求头的 path, params 内容,可以经过我们处理,想干嘛就干嘛

import { Cfetch, interceptors } from './_fetch'

/** 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 
}
let isPending = false;

// 添加请求拦截器 (引入时只会调用一次,储存)
interceptors.request.use((config,path) => { 
    // 获取token
    const token = getToken()  
    let configTemp = Object.assign({ 
        responseType: 'json', 
        headers: { 
            'Content-Type': 'application/json;charset=utf-8', 
            authorization: `${token}` 
        } 
    }, configDefault, config) 
    
    // console.log('添加请求拦截器 config path ==>', config, path)
    // console.log('添加请求拦截器 configDefalut ==>', configDefault)
    
    // /token 这个路径是post请求token的url路径, 直接返回config,不需要其他八卦
    if(path === "/token") {
        return configTemp;
    }
    // expiresIn => 是/token成功后获取一个token过期的时间节点,存在storage
    const expiresIn = getAccessTokenExpiresIn();
    if(new Date().getTime() / 1000 >= expiresIn - 300) {
        // 开关
        if(!isPending) {
          isPending = true;
          // 这里面的参数,按照你们公司大屌的规则来干
          Cfetch(`/token`, {
            method: "post",
            headers: {
                "X-Auth-Token": refreshToken,
                "Content-Type": "application/json",
            },
            body: JSON.stringify({
                "grant_type": "refresh_token",
            })
          }).then(async res => {
            if(res.status == 0) {
              isPending = false;
              // 保存在storage中 (token 以及 token过期的时间节点)
              setAccessTokenAndExpiresIn(res.data.access_token, res.data.expires_in)
            }else {
              isPending = false;
            }
          })
        }
    }
    
    return configTemp 
})

// 添加响应拦截器 (引入时只会调用一次,储存)
interceptors.response.use(async response => {
  // console.log('拦截器response ==>', response)
  
  // 在这里可以做一些操作
  const res = await resultReduction(response.clone())
  // HTTP 状态码 2xx 状态入口,数据正确
  if (response.status >= 200 && response.status < 300) {
    return await response.json()
  // 状态 401 一般token出问题,我把它重定向到path"/" 默认login页面,做处理
  } else if(response.status == 401) {
    return location.href = location.origin;
  } 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
    default:
      res = await response.json()
      break
  }
  return res
}

const 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]
    }
    let url = params ? `${path}?${params}` : `${path}`
    
    return Cfetch(`${url}`, {...configDefault, ...config})
  }
}

const get = () => (path, data, config) {
  return request('GET', path, data, config)
}

const post = () => (path, data, config) {
  return request('POST', path, data, config)
}

export default {
  fetch: Cfetch,
  get,
  post
}

页面调用的时刻到了

import fetchApi from "./_fetch.js";

const response = await fetchApi.post("/token", {username:'admin',password:'147258369'});
const res = await fetchApi.get("/users/token")

参考文献:blog.csdn.net/u010952787/…

参考文献:cloud.tencent.com/developer/a…

参考文献:juejin.cn/post/702144…

参考文献:www.jianshu.com/p/65f03b0b1…

参考文献:www.sandrolain.com/blog/002-va… (英文)

结语

前端react QQ群:788023830 ---- React/Redux - 地下老英雄

前端交流QQ群:249620372 ---- FRONT-END-JS前端

(我们的宗旨是,为了加班,为了秃顶……,仰望大佬),希望小伙伴们加群一起学习