你想知道的关于Axios的知识都在这里

1,902 阅读5分钟

概述

在用Vue进行开发的时候,官方推荐的前后端通信插件是Axios。 Axios是一个基于Promise的HTTP库,可以用在浏览器和Node.js中。

Github开源地址:

github.com/axios/axios

文档:

www.kancloud.cn/yunye/axios…

axios框架,有很多优点,支持Promise,支持拦截请求和响应

在ajax中,请求成功之后,会调用success中的函数处理请求来的数据。那在我们axios中是怎么处理的呢?

axios()本身会返回一个Promise对象,当请求成功的时候,会默认调用resolve()函数,从而你可以把请求成功处理data 的代码放到then方法中执行。同理请求失败的时候,会默认调用reject函数传入错误信息。然后默认调用catch方法

axios源码解析,推荐这一篇文章:

zhuanlan.zhihu.com/p/156862881

使用

安装与导入

npm install axios --save import axios from "axios"

执行 GET 请求

// 为给定 ID 的 user 创建请求
axios.get('/user?ID=12345')
.then(function (response) {
  console.log(response);
})
.catch(function (error) {
  console.log(error);
});
​
// 可选地,上面的请求可以这样做
axios.get('/user', {
  params: {
    ID: 12345
  }
})
.then(function (response) {
  console.log(response);
})
.catch(function (error) {
  console.log(error);
});

执行 POST 请求

axios.post('/user', {
  firstName: 'Fred',
  lastName: 'Flintstone'
})
.then(function (response) {
  console.log(response);
})
.catch(function (error) {
  console.log(error);
});

创建实例

var instance = axios.create({
  baseURL: 'https://some-domain.com/api/',
  timeout: 1000,
  headers: {'X-Custom-Header': 'foobar'}
});

跨域请求

由于浏览器的安全性限制,不允许AJAX访问协议不同、域名不同、端口号不同的数据接口,浏览器认为这种访问不安全。

实现跨域最常用的几种方式:

1.JSONP的实现原理

可以通过动态创建script标签的形式,把script标签的src属性,指向数据接口的地址,因为script标签不存在跨域限制,这种数据获取方式,称作JSONP

2.代理

axios支持代理配置,我们可以通过设置代理来防止跨域问题。

通常在代码中对axios进行全局代理配置,当我们最终把前端代码发布到生产服务器的时候,再通过Nginx等代理服务器来进行请求转发,这样我们的前端代码和后端接口就可以部署在不同的服务器上,也不会产生跨域问题。

在dev开发模式下可以下使用 webpack 的 proxy

以实际项目的配置为例,在vue.config.js文件中的devServer中配置proxy代理请求地址

devServer: {
    // overlay: {
    //   warnings: true,
    //   errors: true
    // },
    // hot: true,//自动保存
    open: true,
    // host: "localhost",
    port: port,
    https: false,
    hot:true,//自动保存
    hotOnly: true,
    disableHostCheck: true,
    proxy: {
      '/api': {
        timeout: 30000, // 请求超时时间
        // target: 'http://122.112.193.171:8086/arcamel-admin',
        pathRewrite:{
          '^/api': ''
        },
        // target : 'http://boot.jeecg.org',
        ws: false, // 是否启用websockets
        // secure: false,
        changeOrigin: true, // 开启代理,在本地创建一个虚拟服务端
        headers: {
          // 'Content-Type': 'application/json'
        },
      }
    }
  },

在生产环境中需要使用 nginx 进行反向代理。nginx.conf文件里中配置

location /api {
    proxy_pass http://122.112.193.171:8084/arcamel-admin;
}

不管是 proxy 和 nginx 的原理都是一样的,通过搭建一个中转服务器来转发请求规避跨域的问题。

3.后端接口跨域支持

后端接口支持跨域,这个也很简单,就是后端程序员(编写接口的),通过过滤器对接口请求进行配置,从而准许接口能够被跨域访问。这样一来,我们前端程序员啥也不用管,直接就可以调用。

Axios封装与拦截器

方便统一处理,简单封装示例:

import axios from "axios"
export function instance1 (config,success,failure){
  let instance = axios.create({});
  instance(config).then(res=>success(res)).catch(err=>failure(err))
}


import axios from "axios" 
// export function instance1 (config,success,failure){ 
// let instance = axios.create({}); 
// instance(config).then(res=>success(res)).catch(err=>failure(err)) 
// }
 export function instance1 (config){ 
   let instance = axios.create({}); 
   return instance(config); 
 }

拦截器

// 添加请求拦截器
axios.interceptors.request.use(function (config) {    
    // 在发送请求之前做些什么    
    return config;
    },
     function (error) {   
  // 对请求错误做些什么   
   return Promise.reject(error);
});

请求时使用application/x-www-form-urlencode

//可以使用qs库来编码数据。示例代码如下:
const qs = require('qs');
axios.post('/foo', qs.stringify({ 'bar': 123 }));
import qs from 'qs';
const data = { 'bar': 123};
const options = {   
    method: 'POST',    
    headers: { 'content-type': 'application/x-www-form-urlencoded' },    
    data: qs.stringify(data),    
    url,
};
axios(options);

以实际项目为例:远程档案预约系统客户端,展示axios的封装和拦截请求的配置

main.js同级目录下,新建http文件夹 main.js文件中引入

import api from './http/index'
Vue.use(api)

http文件夹中新建如下文件: axios.js文件

import axios from 'axios';
import config from './config';
import qs from 'qs';
import { setItem, getItem, removeItem } from "@/utils/store";
import { getToken } from "@/utils/auth";
import { Message } from 'element-ui';
import router from '@/router'
import store from '@/store'
// 使用vuex做全局loading时使用
// import store from '@/store'
export default function $axios(options) {
  return new Promise((resolve, reject) => {
    const instance = axios.create({
      baseURL: config.baseURL,
      headers: {
        'X-Access-Token': getToken()
      },
      transformResponse: [function (data) {
      }]
    })
​
    // request 拦截器
    instance.interceptors.request.use(
      config => {
        let token = getItem('Token')
        // 1. 请求开始的时候可以结合 vuex 开启全屏 loading 动画
        // console.log(store.state.loading)
        // console.log('准备发送请求...')
        // 2. 带上token
        if (token) {
          config.headers.accessToken = token
        }
        return config
      },
​
      error => {
        // 请求错误时
        console.log('request:', error)
        // 1. 判断请求超时
        if (error.code === 'ECONNABORTED' && error.message.indexOf('timeout') !== -1) {
          console.log('timeout请求超时')
          // return service.request(originalRequest);// 再重复请求一次
        }
        // 2. 需要重定向到错误页面
        const errorInfo = error.response
        console.log(errorInfo)
        if (errorInfo) {
          error = errorInfo.data  // 页面那边catch的时候就能拿到详细的错误信息,看最下边的Promise.reject
          const errorStatus = errorInfo.status; // 404 403 500 ...
          router.push({
            path: `/error/${errorStatus}`
          })
        }
        return Promise.reject(error) // 在调用的那边可以拿到(catch)你想返回的错误信息
      }
    )
​
    // response 拦截器
    instance.interceptors.response.use(
      response => {
        let data;
        // IE9时response.data是undefined,因此需要使用response.request.responseText(Stringify后的字符串)
        if (response.data == undefined) {
          data = JSON.parse(response.request.responseText)
        } else {
          data = response.data
        }
​
        // 根据返回的code值来做不同的处理
        switch (data.rc) {
          case 1:
            console.log(data.desc)
            break;
          case 0:
            store.commit('changeState')
            // console.log('登录成功')
          default:
        }
        // 若不是正确的返回code,且已经登录,就抛出错误
        // const err = new Error(data.desc)
        // err.data = data
        // err.response = response
        // throw err
        return data
      },
      err => {
        if (err && err.response) {
          switch (err.response.status) {
            case 400:
              err.message = '请求错误'
              break
            case 401:
              err.message = '未授权,请登录'
              break
            case 403:
              err.message = '拒绝访问'
              break
            case 404:
              err.message = `请求地址出错: ${err.response.config.url}`
              break
            case 408:
              err.message = '请求超时'
              break
            case 500:
              // err.message = '服务器内部错误'
              let res = JSON.parse(err.response.request.response);
              debugger
              if(res.error == 'Internal Server Error'){
                msgInvalidLogin(res.message);
              }
              err.message = '服务器内部错误'
              break
            case 501:
              err.message = '服务未实现'
              break
            case 502:
              err.message = '网关错误'
              break
            case 503:
              err.message = '服务不可用'
              break
            case 504:
              err.message = '网关超时'
              break
            case 505:
              err.message = 'HTTP版本不受支持'
              break
            default:
          }
        }
        console.error(err)
        return Promise.reject(err) // 返回接口返回的错误信息
      }
    )
​
    // 请求处理
    instance(options).then(res => {
      resolve(res)
      return false
    }).catch(error => {
      reject(error)
    })
  })
}

function msgInvalidLogin(msg){
  Message.error(msg);
  let second = 3;
  const timer = setInterval(() => {
    second--;
    if (second) {
    } else {
      clearInterval(timer);
      store.dispatch('LogOut').then(() =>{
        location.reload()// In order to re-instantiate the vue-router object to avoid bugs
      })
    }
  }, 1000);
}

config.js文件,配置请求头

import { getToken } from "@/utils/auth";
export default {
  method: 'get',
  // 基础url前缀
  // baseURL: 'http://localhost:8080/',
  baseURL: window._CONFIG['BASE_API'] ,
  // 请求头信息
  headers: {
    'Content-Type': 'application/json;charset=UTF-8',
    'X-Access-Token': getToken()
  },
  // 参数
  data: {},
  // 设置超时时间
  timeout: 10000,
  // 携带凭证
  withCredentials: true,
  // 返回数据类型
  responseType: 'json'
}

interface.js文件,封装请求接口

import axios from './axios'/*
 * 将所有接口统一起来便于维护
 * 如果项目很大可以将 url 独立成文件,接口分成不同的模块
 */
export const loginByUsername = (username, password, captcha, checkKey) => {
  const data = {
    username,
    password,
    captcha,
    checkKey
  }
  // console.log(data)
  return axios({
    url: '/sys/login',
    method: 'post',
    data
  })
}
​
export const logout = () => {
  return axios({
    url: '/sys/logout',
    method: 'post'
  })
}
​
export const getUserInfo = (token) => {
  return axios({
    url: '/sys/permission/getUserPermissionByToken',
    method: 'get',
    params: { token }
  })
}
​
//post
export const postAction = (url, parameter) => {
  return axios({
    url: url,
    method: 'post',
    data: parameter
  })
}
​
//post method= {post | put}
export const httpAction = (url, parameter, method) => {
  return axios({
    url: url,
    contentType: "application/json; charset=utf-8",
    dateType: "json",
    method: method,
    data: parameter
  })
}
​
//post method= {post | put}
export const httpActionByQuery = (url, query, parameter, method) => {
  return axios({
    url: url,
    method: method,
    params: query,
    data: parameter
  })
}
​
//put
export const putAction = (url, parameter) => {
  return axios({
    url: url,
    method: 'put',
    data: parameter
  })
}
​
//get
export const getAction = (url, parameter) => {
  return axios({
    url: url,
    method: 'get',
    params: parameter
  })
}
/**
 * 获取字典
 * @param {*} data
 */
export function getDictItems(data) {
  return axios({
    url: 'sys/dict/getDictItems/'+ data,
    method: "get"
  });
}
//deleteAction
export const deleteAction = (url, parameter) => {
  return axios({
    url: url,
    method: 'delete',
    params: parameter
  })
}
// 默认全部导出
export default {
  loginByUsername,
  logout,
  getUserInfo,
  postAction,
  httpAction,
  httpActionByQuery,
  putAction,
  getAction,
  getDictItems,
  deleteAction
}

在index.js文件中,导入所有接口后,挂载到Vue 原型的 $api 对象上,可以直接调用 getAction 接口

this.$api.getAction(url, params).then((res) => {
    if (res.success) {
     
    }
});

index.js文件代码:

// 导入所有接口
import apis from './interface'const install = Vue => {
    if (install.installed)
        return;
​
    install.installed = true;
​
    Object.defineProperties(Vue.prototype, {
        // 注意,此处挂载在 Vue 原型的 $api 对象上
        $api: {
            get() {
                return apis
            }
        }
    })
}
​
export default install

除了将接口挂载到Vue 原型的 $api 对象上,还可以在需要的地方直接引入,譬如login.js文件的接口方法引入:


import { loginByUsername } from '@/http/login'
loginByUsername(username, userInfo.password, userInfo.captcha, userInfo.checkKey).then(response => {
  if (response.result) {
   
  }
}).catch(error => {
})
login.js 文件代码:

import axios from './axios'export function loginByUsername(username, password, captcha, checkKey) {
  const data = {
    username,
    password,
    captcha,
    checkKey
  }
  // console.log(data)
  return axios({
    url: '/sys/login',
    method: 'post',
    data
  })
}
​
export function getUserInfo(token) {
  return axios({
    url: '/sys/permission/getUserPermissionByToken',
    method: 'get',
    params: { token }
  })
}

参考书籍:

Vue.js入门与商城开发实战 -- 黄菊华