【Day01】你有封装过 axios 吗?主要是封装哪些方面?如何中断 axios 请求?

一、封装过哪些方面?

在这里插入图片描述
基本逻辑如上,主要是对请求封装和 api 接口封装。

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,用于获取域名地址,实现环境切换可配置

  • 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 的状态来判断是否需要加载这个断网组件。当点击刷新的时候,我们通过跳转 refresh 页面,然后立即返回的方式,来实现重新获取数据的操作。因此,我们需要新建一个 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.");

猜你喜欢

转载自blog.csdn.net/zimeng303/article/details/112253644