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

大河镇楼

结合阮一峰老师的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)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,744评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,505评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,105评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,242评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,269评论 6 389
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,215评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,096评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,939评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,354评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,573评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,745评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,448评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,048评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,683评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,838评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,776评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,652评论 2 354