axios 二次封装 api的统筹管理 配合async await实际项目中的运用

5,907 阅读6分钟
原文链接: www.jianshu.com

写在前面


作者简书地址
axios在实战项目中的运用,所举例项目是基于vue全家桶(vue-router+vuex+axios+element-ui)的后台管理系统,需要一些有vue项目开发经验的读者阅读。
由于vue-resource 作者宣布不再更新,促使我们使用第三方的request数据请求,一般我们的选择有

  1. JQuery ajax
  • 本身是针对MVC的编程,不符合现在前端MVVM的浪潮
  • 基于原生的XHR开发,XHR本身的架构不清晰,已经有了fetch的替代方案
  • JQuery整个项目太大,单纯使用ajax却要引入整个JQuery非常的不合理(采取个性化打包的方案又不能享受CDN服务)
  1. fetch
  • 符合关注分离,没有将输入、输出和用事件来跟踪的状态混杂在一个对象里
    更好更方便的写法
  • 更加底层,提供的API丰富(request, response)
  • 脱离了XHR,是ES规范里新的实现方式
    1)fetchtch只对网络请求报错,对400,500都当做成功的请求,需要封装去处理
    2)fetch默认不会带cookie,需要添加配置项
    3)fetch不支持abort,不支持超时控制,使用setTimeout及Promise.reject的实现的超时控制并不能阻止请求过程继续在后台运行,造成了量的浪费
    4)fetch没有办法原生监测请求的进度,而XHR可以
  1. axios
  • 从浏览器中创建 XMLHttpRequest
  • 从 node.js 发出 http 请求
  • 支持 Promise API
  • 拦截请求和响应
  • 转换请求和响应数据
  • 取消请求
  • 自动转换JSON数据
  • 客户端支持防止CSRF/XSRF

题外话(一)

一般在项目中我们会常常遇到跨域的问题,主要受同源策略(协议,路径,端口)的影响
尤其是使用vue-cli这种脚手架工具开发时,由于项目本身启动本地服务是需要占用一个端口的,所以必然会产生跨域的问题。
在vue-cli项目中webpack的proxyTable帮我们很好的处理了跨域问题:


image.png

如何配置:在项目生成的config目录下的index.js找 有些项目可能要接不同域名下的接口,而proxyTable支持配置多个域名,有这种需求的小伙伴可以自行配置。

划下重点------>
axios的请求会在你的url路径上自动拼接你的target(前提是我这种管理api的方式,后面会提)
记住我画的那个部分(记住这个配置)

 proxyTable: {
      '/api/**': {
        target: 'https://www.jianshu.com:8080/', //==>你的目标域名和端口
        changeOrigin: true,
        pathRewrite: {
          '^/': '/'
        }
      },
    }

主题开始

参考axios-中文使用手册:www.kancloud.cn/yunye/axios…

一、在我们接Api的时候 主要有两个阶段

  • request 请求阶段 ===》对应了axios.interceptors.request 请求拦截器过滤请求
  • response 响应阶段 ===》对应了axios.interceptors.response 响应拦截器过滤响应 我一般在项目中建一个API文件夹 统一管理api 以及 axios的request的封装方法 目录为下


    image.png

下面直接上代码了:

import axios from 'axios'
import { throwErr } from '@/utils' //utils 捕捉服务端http状态码的方法
import store from '@/store'   //引入vuex的相关操作
import { Message } from 'element-ui' //element Toast的提示
import router from '@/router'

//过滤请求
axios.interceptors.request.use(config => {
  //config 为请求的一些配置 例如:请求头 请求时间 Token  可以根据自己的项目需求个性化配置,参考axios的中文说明手册  自己多动动手
 //由于我们项目的后端大大给力,很多东西在服务端帮我们处理好了所以请求阶段只要传好参数就好了
  config.timeout = 10 * 1000 //请求响应时间
  return config
}, error => {
  return Promise.reject(error)
})
// 添加响应拦截器
axios.interceptors.response.use(
  response => {
    if (response.data.code === 0) {   //服务端定义的响应code码为0时请求成功
      return Promise.resolve(response.data) //使用Promise.resolve 正常响应
    } else if (response.data.code === 1401) { //服务端定义的响应code码为1401时为未登录
      store.dispatch('setUserInfo', {})
      Message({
        message: '未登录'
      })
      // router.push('/login')
      return Promise.reject(response.data)    //使用Promise.reject 抛出错误和异常
    } else {
      return Promise.reject(response.data)
    }
  },
  error => {
    if (error && error.response) {
      let res = {}
      res.code = error.response.status
      res.msg = throwErr(error.response.status, error.response) //throwErr 捕捉服务端的http状态码 定义在utils工具类的方法
      return Promise.reject(res)
    }
    return Promise.reject(error)
  }
)
export default function request(method, url, data) {  //暴露 request 给我们好API 管理
  method = method.toLocaleLowerCase()   //封装RESTful API的各种请求方式 以 post get delete为例
  if (method === 'post') {
    return axios.post(url, data)    //axios的post 默认转化为json格式
  } else if (method === 'get') {     
    return axios.get(url, {
      params: data
    })
  } else if (method === 'delete') {
    return axios.delete(url, {
      params: data
    })
  }
}

以下为throwErr的源码

//axios捕错
export const throwErr = (code, response) => {
  let message = '请求错误'
  switch (code) {
    case 400:
      message = '请求错误'
      break
    case 401:
      message = '未授权,请登录'
      break
    case 403:
      message = '拒绝访问'
      break
    case 404:
      message = `请求地址出错: ${response.config.url}`
      break
    case 408:
      message = '请求超时'
      break
    case 500:
      message = '服务器内部错误'
      break
    case 501:
      message = '服务未实现'
      break
    case 502:
      message = '网关错误'
      break
    case 503:
      message = '服务不可用'
      break
    case 504:
      message = '网关超时'
      break
    case 505:
      message = 'HTTP版本不受支持'
      break
    default:
  }
  return message
}

如果有的小伙伴 是'application/x-www-form-urlencoded'为请求格式的话 可以使用 npm i qs -S 使用qs包将参数序列化

  transformRequest:[function(data){
      //在这里根据自己的需求改变数据
      return qs.stringify(data,{arrayFormat:'repeat'})
    }],

二 在api管理文件夹中如何定义接口呢?

以account.js为例

  • 引入request.js
  • 暴露你定义的api接口
  • request 的三个参数(mtehod,url,params)
    1. method 及axios的请求方法 (post,get,delete ···Ï)
    2. url 服务端的接口路径
      3.params 接口所传递的参数 全部以对象的形式传入 (后面会提 不着急)
image.png
import request from './request' //引入axios的封装方法

export const getAdminList = (params) => {
 return request('get', '/api/v1.0/admin/list', params) //登陆管理员获取自身信息
}

export const register = (params) => {
 return request('post', '/api/v1.0/admin/register', params) //添加管理员
}

export const deleteAdmin = (id, params) => {
 return request('delete', `/api/v1.0/admin/${id}`, params) //更新管理员信息
}

是不是很简单 管理的接口就是这么简洁帅气 为我打个call

在项目中如何使用定义的接口?

1.在页面中import对应的模块api文件以及其中的接口
2.不管是哪种请求 我们全部用对象(键值对)的形式传参数
3.用async 和await 在methods中定义接口方法

  • 用来区分我们所定义的普通方法
  • 可以方便我们处理函数回调 用同步的思想写代码 方便理解
  • 不用.then那种链式写法 美化代码 增加可读性和理解性
  • 配合try catch用来捕错
image.png

此列子可以在我的上篇关于 vue mixin的文章结合观看 观码效果更加
传送门

 getParams() {
      return {
        role: this.role,
        name: this.name,
        status: this.status,
        start: (this.PAGINATION.currentPage - 1) * this.PAGINATION.pageSize,
        range: this.PAGINATION.pageSize,
      }
    },
    async getAdminList() {
      this.loading = true
      let res = await getAdminList(this.queryParams)
      this.tableData = res.data.list
      this.PAGINATION.total = res.data.count
      this.loading = false
    },
  async deleteAdmin() {
      try {
        let res = await deleteAdmin(this.id)
        this.$message({
          type: 'success',
          message: '删除成功'
        })
        this.getAdminList()
      } catch (err) {
        this.$message({
          type: 'error',
          message: err.msg
        })
      }
    },
 addParams() {
      let { role, username, name, password, gender, profession, education, signature } = this.form
      return { role, username, name, password, gender, profession, education, signature }
    },
 async register() {
      try {
        let res = await register({ ...this.addParams() })
        this.$message({
          type: 'success',
          message: '添加成功'
        })
        this.dialogFormVisible = false
        this.$emit('listen')
      } catch (err) {
        this.$message({
          type: 'error',
          message: err.msg
        })
      }
    },

在实际表现如下 1.get 操作表现


image.png

2.delete操作表现


image.png

3.post 操作


image.png

题外话(二)

关于RESTful API的理解 (主要是后端开发者观看,前端小伙伴了解即可)

精简点:

  • http的动词(get post delete put patch ···)描述操作
  • url地址定位资源

RESTful API:

这是一种设计风格而不是标准,只是提供了一组设计原则和约束条件。它主要用于客户端与服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层级,更易于实现缓存等机制。在这种风格中,每个url路径代表一种资源(resource),所以路径中不推荐有名词,而且所用的名词往往与数据库的表格名对应,且一般采取复数的形式命名。而对应资源的具体操作类型,而由HTTP动词表示,即 GET/POST/PUT/PATCH/DELETE

image.png

这是我在看书过程中所阅读到的,具体书名就不透露了,免得有打广告嫌疑

写在后面

以上就是axios的二次封装以及如何在项目中如何优雅的使用,统一使用对象传参的方式。我觉得这种方式很方便优雅。因此将它分享出来,希望大家会喜欢。觉得有用的小伙伴可以给个心,给个关注,有不懂的地方可以评论我和私信我,有空会一一解答。下次会分享一篇在vue中如何将tinyMce封装成组件,tinyMCe富文本编辑器如何上传图片和文件。
发现简书的百度seo不怎么好而且不防爬虫,第二天搜索自己的标题发现文章在其它网站上而且是第一条,根本搜不到自己的
简书首发网址