美文网首页
(偷师向)网络层架构与async+axios+vuex使用心得

(偷师向)网络层架构与async+axios+vuex使用心得

作者: 人猿Jim | 来源:发表于2020-05-16 17:46 被阅读0次
    大河镇楼

    结合阮一峰老师的es6入门,记录一下自己对于async的使用心得
    在用async之前,最好先熟悉Promise,毕竟主流的async都是基于Promise来操作的

    async注意事项

    1.async函数的返回值是 Promise
    2.async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误
    3.async函数内部的异步操作执行完,才会执行then方法指定的回调函数

    await 注意事项

    1.await命令后面是一个 Promise 对象,返回该对象的结果
    2.任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行,由此引出try catch 结构

    (此块特别重要,故引用官网强调)

    async function f() {
      try {
        await Promise.reject('出错了');
      } catch(e) {
      }
      return await Promise.resolve('hello world');
    }
    
    f()
    .then(v => console.log(v))
    // hello world 
    

    以上用法在es6官网吃透,就可以去项目实战,下面说一下我项目用(偷师)到的vue+axios+async+antdUI的网络层架构。


    珂朵莉的分界线

    网络层项目架构如下图:

    网络层项目架构
    目录说明:

    http:封装axios
    mock:mockjs
    service:整合请求路径,组成具体的网络请求方法
    store:调用网络请求,保存请求数据的场所

    1.除vuex store外,最外层的index.js负责统一的导入导出,这样的好处是页面引用时都是import相对相同位置的index.js 文件

    2.service层中api.js 负责记录请求路径,具体请求方法在各个模块文件夹的index.js中,然后在index.js统一导入导出

    3.请求数据保存在vuex中的原因是方便存取,并且可以管理关于网络层的全局状态

    然后页面在调用的时候,你只需要这样:

    // 调用方法
    this.$store.dispatch('demoModule1Module/demoList', {...params})
      .then(res=>res)
    // 取数据
    const data = this.$store.getters['demoModule1Module/demoList']
    

    各模块详情

    axios 部分

    主流前端网络请求库,想学深入可以去看源码,贴一个实例

    import axios from 'axios'
    // 请求序列化工具
    import qs from 'qs'  
    // 项目全局配置参数
    import { apiBaseUrl, isDebug, accessToken, loginPage } from '@/config/index'
    // 统一打印
    import Console from '@/utils/Console.js'
    // vuex
    import store from '@/store/index.js'
    // 自定义获取token的方法
    import { getAuthToken } from '@/utils/commont.js'
    // 错误统一提示
    import { message } from 'ant-design-vue'
    
    const NODE_ENV = process.env.NODE_ENV
    // console.log(NODE_ENV)
    let baseURL = apiBaseUrl.proBaseUrl
    if (NODE_ENV === 'development') { // development or production
      baseURL = apiBaseUrl.devBaseUrl
    }
    
    // 1.创建一个axios的实例
    const instance = axios.create({
      baseURL, //  /category ; /home/data?type=pop&page=1
      timeout: 30000 // 30 s
      // timeout: 600000 // 10 min
    })
    
    // 2.添加默认的配置
    // instance.defaults.headers.common['Authorization'] = AUTH_TOKEN;
    
    instance.defaults.transformRequest = [function(data, config) {
      if (!config['Content-Type']) return qs.stringify(data)
      switch (config['Content-Type'].toLowerCase()) {
        // 上传json对象
        case 'application/json;charset=utf-8': {
          return JSON.stringify(data)
        }
        // multipart/form-data;charset=utf-8 上传文件
        case 'multipart/form-data;charset=utf-8': {
          return data
        }
        default: {
          // 提交表单
          return qs.stringify(data)
        }
      }
    }]
    
    // 3.拦截请求
    instance.interceptors.request.use(config => {
      store.commit('changeLoading', true)
      // 给所有的请求头:统一添加auth_token
      if (isDebug) {
        Console.log(accessToken)
        config.headers.auth_token = accessToken
      } else {
        config.headers.auth_token = getAuthToken()
      }
      return config
    }, error => {
      return Promise.reject(error.data.error.message)
    })
    
    // 4.拦截响应 与后台约定处理状态返回在data.code中
    instance.interceptors.response.use(response => {
      store.commit('changeLoading', false)
      Console.log('response=', response)
      const data = response.data
      if (data) {
        if (data.code === 500 || data.code === 1001 || data.code === 403) {
          // message.error(data.msg + ',code=' + data.code)
          message.error(data.msg)
          // 登录过期返回 403
          if (data.code === 403) {
            // 重定向到指定的url中
            if (!isDebug) {
              // window.location.href = data.data.url
              // window.location.href = loginPage
              window.parent.location.href = loginPage
            }
          }
          return response
        }
      } else {
        // message.error('接受的resp.data为空,status=' + response.status)
        message.error('接受的resp.data为空')
      }
      return response
    }, error => {
      store.commit('changeLoading', false)
      Console.log('error=', error)
      // const config = error.config
      // 判断错误url是否为请求接口*/
      if (error.response.status === 404) {
        // message.error('请求的url不存在,status=' + error.response.status)
        message.error('请求的url不存在')
        return Promise.reject(error)
      }
    
      // 判断返回的状态码为400
      if (error.response.status === 403) {
        // message.error('服务禁止请求该资源,status=' + error.response.status)
        message.error('服务禁止请求该资源')
        return Promise.reject(error)
      }
    
      // message.error('网络请求错误error,status=' + error.response.status)
      message.error('网络请求错误error')
      // 判断返回的状态码为403
      return Promise.reject(error)
    })
    
    export const axiosInstance = axios
    
    /**
     * 封装一个get请求
     * @param url     // /home?name=xxx&age=xx
     * @param params  // {name:"keduoli",age:12 }
     * @param config  //  {baseURL:'http://xxxx',timeout: 5000,headers: {'X-Requested-With': 'XMLHttpRequest'}, ... }
     * @return promise
     */
    export const httpGet = (url, params, config) => {
      params = params || {}
      config = config || {}
      config = Object.assign(config, { params: params })
      // Console.log(url, params)
      return instance.get(url, config)
    }
    
    /**
     * 封装一个post请求
     * @param url   // /home
     * @param data  // { name:"珂朵莉",age:12 } or FormData
     * @param config // {baseURL:'http://xxxx',timeout: 5000,headers: {'X-Requested-With': 'XMLHttpRequest'}, ... }
     * @return promise
     */
    export const httpPost = (url, data, config) => {
      Console.log(url, config)
      config = config || {}
      return instance.post(url, data, config)
    }
    

    统一导入导出index.js

    import { axiosInstance as axios, httpGet, httpPost, httpPut, httpDelete } from './axios.js'
    export {
      axios,
      httpGet,
      httpPost,
      httpPut,
      httpDelete
    }
    

    service 部分

    此块导入操作比较多,需要理清思路,一旦项目变大,这种模式的管理会比较规范且容易管理
    这里以user模块为例,介绍一下具体操作
    api.js

    /**
     * =======================================================
     *  管理各模块的请求路径  这里面导出的对象会在 service/xx中使用
     * =======================================================
     */
    export const UserApi = {
      // test
      login: '/api/4/news/latest'
    }
    
    export const demoModule1Api = {
      // test
      login: '/api/4/news/latest'
    }
    
    export const demoModule2Api = {
      // test
      login: '/api/4/news/latest'
    }
    

    user/index.js

    import { UserApi } from '@/service/api.js' // 引用api记录的请求路径
    import { httpGet } from '@/http/index.js'
    /*
     *用户中心的服务类( 在service/index.js中统一导出 )
    */
    export default class UserService {
      /**
       * 登录接口(没用到)
       * @param {} params 登录提交的参数
       */
      async login(params) {
        const primose = await httpGet(UserApi.login, params) // httpGet(UserApi.login) 返回的是promise对象
        return primose
      }
    }
    

    最外层 service/index.js

    import UserService from '@/service/user/index.js'
    import demoModule1Service from '@/service/upload/index.js'
    import demoModule2Service from '@/service/upload/index.js'
    
    const userService = new UserService()
    const demoModule1Service = new demoModule1Service()
    const demoModule2Service = new demoModule2Service()
    
    /**
     * 这里导出的service对象,会在对应的store/xxxx/ 的action中导入使用 或者 页面组件中导入使用
     */
    export {
      userService,
      demoModule1Service,
      demoModule2Service
    }
    

    定义网络请求的具体操作是现在api记录请求路径,然后在模块index的class类下写请求方法然后再到最外层index导出这个class类,具体使用请看vuex层

    vuex部分

    这一块主要是网络方法的调用与参数传递
    使用require.context实现前端工程自动化,自动导入modules

    /**
     * 自动加载store的二级 modules
     */
    const modules = {}
    const allModules = require.context('@/store/modules/', true, /index\.(js|ts)$/)
    // ["./login/index.js", "./main/books/index.js", "./main/goods/index.js", "./register/index.js"]
    console.log('allModules=', allModules.keys())
    allModules.keys().forEach((item, index, array) => {
      // item = ./login/index.js => login/index.js
      // item = ./main/books/index.js => main/books/index.js
      const module_path = item.substr(2)
      const moduleNames = module_path.split('/') // [login, index.js] - [main, books, index.js]
      moduleNames.pop()
      const key = moduleNames.join('_')
    
      // const module = require(`@/store/modules/${module_path}`)
      const module = allModules(item)
      modules[key] = module.default
    })
    /**
     * modules:{
        goods :{
             // 0.启用命名空间
             namespaced: true,
             // 1.定义状态
             state: {
               data: {}, // 列表数据
               recordDetail: {} // 详情数据
             },
             // 2.修改状态
             mutations: {
               // 这里的 `state` 对象是模块的局部状态
               [Types.addData](state, payload) {
                 state.data = payload
               },
               [Types.recordDetail](state, payload) {
                 state.recordDetail = payload
               }
             },
             // 3.提交action,来修改状态
             actions: {
               async list(context, payload) {
                 // context 对象 与 store对象有相同的方法;context != store
                 // 注意:局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState
                 const config = {
                   headers: {
                     'Content-Type': 'application/json;charset=utf-8'
                   }
                 }
                 try {
                   const result = await interrogaterecordService.list(payload, config)
                   console.log('result=', result)
                   context.commit(Types.addData, result.data.data)
                   return Promise.resolve(result.data.data)
                 } catch (err) {
    
                 }
               },
               async recordDetail(context, payload) {
                 // context 对象 与 store对象有相同的方法;context != store
                 // 注意:局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState
                 const config = {
                 }
                 try {
                   const result = await interrogaterecordService.recordDetail(payload, config)
                   Console.log('recordDetail=', result)
                   context.commit(Types.recordDetail, result.data.data)
                   return Promise.resolve(result.data)
                 } catch (err) {
    
                 }
               }
             },
             // 4.获取定义的状态, 通过store.getters获取里面的函数,例如:store.getters.count
             getters: {
               // state 是获取局部状态;rootState是获取根状态
               ...ComGetters, // list --> data.content ; listPageConfig -> pageConfig
               recordDetail(state, getters, rootState, rootGetters) {
                 return state.recordDetail || {}
               }
             }
           }
     * }
     */
    export default modules || {}
    

    顶层store(store/index.js)

    import Vue from 'vue'
    import Vuex from 'vuex'
    import demoModule1Module from './modules/demo/index.js'
    import GlobalType from './global-types.js'
    Vue.use(Vuex)
    
    export default new Vuex.Store({
      // 1.定义状态(提前定义的状态才有响应式的功能)
      state: {
        count: 0,
        // 全局加载进度
        loading: false,
      },
      // 2.修改状态(明确地追踪到状态的变化),通过store.commit('increment')事件触发
      mutations: {
        increment(state, payload) {
          state.count++
        },
        [GlobalType.changeLoading](state, payload) {
          state.loading = payload || false
        },
        [GlobalType.fileBaseUrl](state, fileBaseUrl) {
          state.fileBaseUrl = fileBaseUrl
        }
      },
      // 3.提交action,来修改状态,通过store.dispatch('increment')事件触发
      actions: {
        increment(context, payload) {
          // context 对象 与 store对象有相同的方法;context != store
          context.commit('increment')
        },
        isShowLoading(context, payload) {
          // context 对象 与 store对象有相同的方法;context != store
          context.commit(GlobalType.changeLoading, payload)
        }
      },
      // 4.获取定义的状态, 通过store.getters获取里面的函数,例如:store.getters.count
      getters: {
        count(state, getters) {
          return state.count
        },
        loading(state, getters) {
          return state.loading
        }
      },
      // 5.将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块
      modules: {
        demoModule1Module, // 测试模块
      }
    })
    

    子模块(store/demoModule1/index.js)

    import demoModuleTypes from './inmates-types.js' // mutations和actions的统一名字映射
    import { demoModule1Service } from '@/service/index.js'
    import Console from '@/utils/Console.js'
    
    // state 的 demoModule 模块 ( 这个模块在store/index.js 中的module使用 )
    
    const demoModule1Module = {
      // 0.启用命名空间
      namespaced: true,
      // 1.定义状态
      state: {
        demoData: {}
      },
      // 2.修改状态
      mutations: {
        // 这里的 `state` 对象是模块的局部状态
        [demoModuleTypes.addDemoData](state, payload) {
          state.demoData = payload
        }
      },
      // 3.提交action,来修改状态
      actions: {
        async demoList(context, payload) {
          // context 对象 与 store对象有相同的方法;context != store
          // 注意:局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState
          const config = {
            headers: {
              'Content-Type': 'application/json;charset=utf-8'
            }
          }
          try {
            const result = await demoModule1Service.test(payload, config)
            Console.log('result=', result)
            context.commit(demoModuleTypes.addDemoData, result.data.data)
            return Promise.resolve(result.data.data)
          } catch (err) {
            // 进行错误处理
          }
        }
      },
      // 4.获取定义的状态, 通过store.getters获取里面的函数,例如:store.getters.count
      getters: {
        // state 是获取局部状态;rootState是获取根状态
        demoList(state, getters, rootState, rootGetters) {
          return state.demoData.content || []
        }
    
      }
    }
    export default demoModule1Module
    

    配置好vuex层后,网络这块就大功告成了!

    完毕

    (最近加班到肝疼T_T)

    相关文章

      网友评论

          本文标题:(偷师向)网络层架构与async+axios+vuex使用心得

          本文链接:https://www.haomeiwen.com/subject/mkzlohtx.html