axios使用和源码阅读

Axios是近年来备受推崇的一个网络请求库,它以基于Promise的方式封装了浏览器的XMLHttpRequest和服务器端node http请求,使得我们可以用es6推荐的异步方式处理网络请求。
功能特性:

  • 从浏览器创建XMLHttpRequest
  • 从node.js创建http请求
  • 支持Promise API
  • 拦截请求与响应
  • 转换请求与响应数据
  • 取消请求
  • 自动转换JSON数据
  • 支持客户端XSRF攻击防护

axios.interceptors

axios.interceptors拦截器主要用来作什么?和路由拦截有什么区别
  • 一个是接口的request, response数据处理
  • 一个是对业务处理,比如页面权限控制

axios拦截

axios拦截器分为请求拦截器和响应拦截器。用户可以通过then方法为请求添加回调,而拦截器中的回调将在then中的回调之前执行

// 添加请求拦截器
axios.interceptors.request.use(function (config) {
    // Do something before request is sent
    return config;
  }, function (error) {
    // Do something with request error
    return Promise.reject(error);
  });

// 添加响应拦截器
axios.interceptors.response.use(function (response) {
    // Do something with response data
    return response;
  }, function (error) {
    // Do something with response error
    return Promise.reject(error);
  });

移除已经设置的拦截器

var myInterceptor = axios.interceptors.request.use(function () {/*...*/});
axios.interceptors.request.eject(myInterceptor);

给自定义的axios实例添加拦截器

var instance = axios.create();
instance.interceptors.request.use(function () {/*...*/});
import axios from 'axios'
import env from '@/env'
import Utils from '@/components/Utils'
import {
  Message
} from 'element-ui'

// 获取系统ID
const systemId = Utils.getQueryString('systemId')

if (systemId) {
  window.systemId = systemId
} else {
  // 没有系统ID
  location.href = env.dataCloud
}

const auth = (response) => {
  // 未登录
  if (response && ((response.status === 401) || (response.status === 412))) {
    location.href = env.dataCloud
  }
}

const instance = axios.create({
  baseURL: `${env.api}${env.prefix.develop}`,
  params: {
    systemId
  },
  withCredentials: true
})
instance.nterceptors.response.use((response) => {
  auth(response)
  if (response && response.data && response.data.statusCode !== '0') {
    Message.error((response.data && response.data.msg) || response.data.errmsg || '抱歉')
    return Promise.reject(response).catch(() => { })
  }
  return Promise.resolve(response.data)
}, (error) => {
  let msg = '网络错误,请稍后再试'
  if (error && error.response && error.response.status === 401) {
    msg = '请登录'
  }
  const notice = document.getElementsByClassName('ivu-message-error')
  if (notice.length === 0) {
    Message.error(msg)
  }
  auth(error && error.response)
  return Promise.reject(error).catch(() => { })
})

export default instance

路由拦截

login.js

clipboard.png

main.js
clipboard.png

axios/lib/core/Axios.js
clipboard.png
axios/lib/core/InterceptorManager.js
clipboard.png

拦截器上有request,response。分别用于拦截发送,接收。

经典场景

比如这样一个场景:

在进行敏感操作(常见敏感操作如购买获取列表等)之前,每个请求需要携带token,但是token 有有效期,token 失效后需要换取新的token并继续请求。

需求分析:

每个请求都需要携带 token ,所以我们可以使用 axios request 拦截器,在这里,我们给每个请求都加 token,这样就可以节省每个请求再一次次的复制粘贴代码。
token 失效问题,当我们token 失效,我们服务端会返回一个特定的错误表示,比如 token invalid,但是我们不能在每个请求之后去做刷新 token 的操作呀,所以这里我们就用 axios response 拦截器,我们统一处理所有请求成功之后响应过来的数据,然后对特殊数据进行处理,其他的正常分发。

功能实现

在 main.js 注册 axios

jsVue.use(Vuex)
Vue.use(VueAxios, axios)
Vue.use(qs)
注:qs,使用axios,必须得安装 qs,所有的Post 请求,我们都需要 qs,对参数进行序列化。

在 request 拦截器实现

axios.interceptors.request.use(
 config => {
  config.baseURL = '/api/'
  config.withCredentials = true // 允许携带token ,这个是解决跨域产生的相关问题
  config.timeout = 6000
  let token = sessionStorage.getItem('access_token')
  let csrf = store.getters.csrf
  if (token) {
   config.headers = {
    'access-token': token,
    'Content-Type': 'application/x-www-form-urlencoded'
   }
  }
  if (config.url === 'refresh') {
   config.headers = {
    'refresh-token': sessionStorage.getItem('refresh_token'),
    'Content-Type': 'application/x-www-form-urlencoded'
   }
  }
  return config
 },
 error => {
  return Promise.reject(error)
 }
)
//在 response 拦截器实现

axios.interceptors.response.use(
 response => {
  // 定时刷新access-token
  if (!response.data.value && response.data.data.message === 'token invalid') {
   // 刷新token
   store.dispatch('refresh').then(response => {
    sessionStorage.setItem('access_token', response.data)
   }).catch(error => {
    throw new Error('token刷新' + error)
   })
  }
  return response
 },
 error => {
  return Promise.reject(error)
 }
)

源码分析

[libcoreInterceptorManager.js L5]()

'use strict';

var utils = require('./../utils');

function InterceptorManager() {
  this.handlers = [];
}

/**
 * Add a new interceptor to the stack
 *
 * @param {Function} fulfilled The function to handle `then` for a `Promise`
 * @param {Function} rejected The function to handle `reject` for a `Promise`
 *
 * @return {Number} An ID used to remove interceptor later
 */

//  InterceptorManager的对象中是通过handlers数组变量存储拦截器,
//  数组每项同时包含了分别作为Promise中resolve和reject的回调。
// InterceptorManager类中还包含了对该数组变量的添加、 移除、 遍历方法。
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected
  });
  return this.handlers.length - 1;
};

/**
 * Remove an interceptor from the stack
 *
 * @param {Number} id The ID that was returned by `use`
 */

// 移除方法是通过直接将该项设为null实现的, 
// 而不是用splice剪切该数组, 遍历方法中也增加了相应的null值处理。 
// 这样做一方面使得每一项ID保持为项的数组索引不变, 
// 另一方面也避免了重新剪切拼接数组的性能损失。
InterceptorManager.prototype.eject = function eject(id) {
  if (this.handlers[id]) {
    this.handlers[id] = null;
  }
};

/**
 * Iterate over all the registered interceptors
 *
 * This method is particularly useful for skipping over any
 * interceptors that may have become `null` calling `eject`.
 *
 * @param {Function} fn The function to call for each interceptor
 */
InterceptorManager.prototype.forEach = function forEach(fn) {
  utils.forEach(this.handlers, function forEachHandler(h) {
    if (h !== null) {
      fn(h);
    }
  });
};

module.exports = InterceptorManager;

参考

vuex使用axios拦截器及持久化

vue-axios interceptors(拦截器)实际应用
一个项目学会前端实现登录拦截
axios-explore

Vue二次封装axios

阅读 619

推荐阅读
镜心的小树屋
用户专栏

方寸湛然GitHub组织地址:[链接]

47 人关注
123 篇文章
专栏主页