封装 reuqest

104 阅读4分钟

1.1 http 封装

http 封装其实就是实例化一个 axios 对象,并对其进行一些配置:

  • 1、设置请求超时
  • 2、post 头设置
  • 3、请求拦截
  • 4、响应拦截
  • 5、重复请求取消(这里涉及到了如何取消请求)
  • 6、错误处理
  • 7、断网处理
  • 8、工具函数
import axios from 'axios'
import router from '../router'
import store from '../store/index'
import { Toast } from 'vant'

//提示函数
const tip = msg => {
    Toast({
        message: msg,
        duration: 1000,
        forbidClick: true
    })
}
//跳转登录页,携带当前页面路由,登录后返回当前页面
const toLogin = () => {
    router.replace({
        path: '/login',
        query: {
            redirect: router.currentRoute.fullPath
        }
    })
}
//错误处理
const errorHandle = (status, other) => {
    switch (status) {
        //未登录
        case 401:
            toLogin();
            break;
        //403 token过期,清除token并跳转登录页
        case 403:
            tip('登录过期,请重新登录');
            localStorage.removeItem('token');
            store.commit(loginSuccess, null);
            setTimeout(() => {
                toLogin();
            }, 1000);
            break;
        //请求不存在
        case 404:
            tip('请求资源不存在');
            break;
        default:
            console.log(other)
    }
}

// 用于存储目前状态为pending的请求标识信息
let pendingRequest = [];

//取消请求-请求拦截中的处理
const CancelToken = config => {
    // 区别请求的唯一标识,这里用方法名+请求路径
    const requestMark = `${config.method} ${config.url}`;
    // 找当前请求的标识是否存在pendingRequest中,即是否重复请求了
    const markIndex = pendingRequest.findIndex(item => {
        return item.name === requestMark;
    });
    // 存在,即重复了
    if (markIndex > -1) {
        // 取消上个重复的请求
        pendingRequest[markIndex].cancel();
        // 删掉在pendingRequest中的请求标识
        pendingRequest.splice(markIndex, 1);
    }
    // (重新)新建针对这次请求的axios的cancelToken标识
    const CancelToken = axios.CancelToken;
    const source = CancelToken.source();
    config.cancelToken = source.token;
    // 设置自定义配置requestMark项,主要用于响应拦截中
    config.requestMark = requestMark;
    // 记录本次请求的标识
    pendingRequest.push({
        name: requestMark,
        cancel: source.cancel,
    });
    return config;
};

//取消请求-响应拦截中的处理
const CancelTokenResponse = config => {
    // 根据请求拦截里设置的requestMark配置来寻找对应pendingRequest里对应的请求标识
    const markIndex = pendingRequest.findIndex(item => {
        return item.name === config.requestMark;
    });
    // 找到了就删除该标识
    markIndex > -1 && pendingRequest.splice(markIndex, 1);
}

//创建axios实例
var instance = axios.create({
    timeout: 1000 * 12
})

//设置post请求头
instance.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';

//请求拦截器
instance.interceptors.request.use(
    config => {
        config = CancelToken(config);
        const token = store.state.token;
        token && (config.headers.Authorization = token);
        return config;
    },
    error => Promise.reject(error);
)
//响应拦截器
instance.interceptors.response.use(
    res => {
        //请求结束就从pendingRequest删除请求标志
        CancelTokenResponse(res.config);
        return res.status === 200 ? Promise.resolve(res) : Promise.reject(res);
    },
    error => {
        const { response } = error;
        //请求已经发出,返回结果不在2xx的范围
        if (response) {
            CancelTokenResponse(response.config);
            errorHandle(response.status, response.data.message);
            return Promise.reject(response);
        } else {
            //断网情况,刷新重新获取数据
            if (!window.navigator.onLine) {
                store.commit('changeNetwork', false);
            } else {
                return Promise.reject(error);
            }
        }
    }
)

export default instance;

1.2 api 封装

api 封装主要是用于单个模块所需要的接口进行管理。其中包括了

  • 1、总 api 接口的映射
  • 2、环境变量的切换
  • 3、本地 mock 功能
  • 4、单个模块的接口列表
1.2.1 总 api 接口的映射

api 中定义一个 index.js 用于所有模块接口的管理

//api接口统一出口
import user from "./user";
import productList from "./product";
//...

export { user, productList };
1.2.2 建立一个 get_url.js,用于获取域名地址,实现环境切换可配置
import config from "@/config";
import urlMap from "@/config/urlMap";

//api控制接口类型,1为远程接口,0为mock接口
export default function getUrl(url, api = 1) {
  return api === 0 ? urlMap[url] : config.apiDomain + url;
}

新建一个配置文件夹 config,其中的 index 文件用于切换环境,urlMap 用于本地 mock 地址的映射

//config/index.js
/**
 * 配置编译环境和线上环境之间的切换
 * 默认三套可以增添
 */

let apiDomain;
switch (process.env.NODE_ENV) {
  case "dev":
    apiDomain = "https://www.dev.com";
    break;
  case "prod":
    apiDomain = "https://www.prod.com";
    break;
  case "test":
    apiDomain = "https://www.test.com";
    break;
}
export { apiDomain };
//urlMap.js
/**
 * 远程接口地址和本地mock地址映射表
 * key:接口地址
 * value:本地地址
 */
const mockBaseUrl = "http://rap2api.taobao.org/app/mock";
export default {
  "/user/login": mockBaseUrl + "/223948/login",
  "/user/info": mockBaseUrl + "/223948/info",
  "/user/logout": mockBaseUrl + "/223948/logout",
};
1.2.3 单个模块接口定义
//产品列表接口
import axios from "@/http";
import getUrl from "@/api/get_url";

const product = {
  // 新闻列表
  productList() {
    return axios({
      url: getUrl("/topics"),
      method: "get",
    });
  },
  // 新闻详情,演示
  productDetail(id, params) {
    return axios({
      url: getUrl(`/topic/${id}`),
      params: params,
      method: "get",
    });
  },
  // post提交
  login(data) {
    return axios({
      url: getUrl(`${base.sq}/accesstoken`),
      method: "post",
      data,
    });
  },
  //...更多接口
};

export default product;
1.2.4 断网处理
  • 1、http 封装中,当断网时,会对 Vue 的网络状态进行更新
  • 2、在 App.vue 中,根据网络情况,判断是否需要加载断网组件
  • 3、全局定义一个断网组件,实现跳转重新获取页面的操作
<!-- App.vue -->
<template>
  <div id="app">
    <div v-if="!network">
      <h3>我没网了</h3>
      <div @click="onRefresh">刷新</div>
    </div>
    <router-view />
  </div>
</template>

<script>
  import { mapState } from "vuex";
  export default {
    name: "App",
    computed: {
      ...mapState(["network"]),
    },
    methods: {
      // 通过跳转一个空页面再返回的方式来实现刷新当前页面数据的目的
      onRefresh() {
        this.$router.replace("/refresh");
      },
    },
  };
</script>

http.js中断网的时候,会更新 vue 中 network 的状态。此时根据 network 的状态来判断是否需要加载这个断网组件。当点击刷新的时候,我们通过跳转 refesh 页面然后立即返回的方式来实现重新获取数据的操作。因此我们需要新建一个refresh.vue页面,并在其beforeRouteEnter钩子中再返回当前页面。

// refresh.vue
beforeRouteEnter (to, from, next) {
    next(vm => {
        vm.$router.replace(from.fullPath)
    })
}

1.3 将 api 挂载到全局

//main.js
import Vue from "vue";
import App from "./App";
import router from "./router"; // 导入路由文件
import store from "./store"; // 导入vuex文件
import api from "./api"; // 导入api接口

Vue.prototype.$api = api; // 将api挂载到vue的原型上

1.4 中断 axios 请求

上面避免重复请求中,就使用到了 cancel token 取消请求,使用 CancelToken 工厂方法创建 cancel token

const CancelToken = axios.CancelToken;
const source = CancelToken.source();

axios
  .get("/user/12345", {
    cancelToken: source.token,
  })
  .catch(function (thrown) {
    if (axios.isCancel(thrown)) {
      console.log("Request canceled", thrown.message);
    } else {
      // 处理错误
    }
  });

axios.post(
  "/user/12345",
  {
    name: "new name",
  },
  {
    cancelToken: source.token,
  }
);

// 取消请求(message 参数是可选的)
source.cancel("Operation canceled by the user.");