axios中的請求攔截(防止屢次點擊按鈕屢次請求形成的帶寬浪費)

axios中的請求攔截(防止屢次點擊按鈕屢次請求形成的帶寬浪費)

原理:axios請求中有一個cancel token的api

在網上找了許多請求的攔截,但不少都是在請求開始時進行數組存儲CancelToken方法與比對信息,返回後進行處理。按照其代碼實現了一遍,結果發現雙擊兩次能夠實現請求cancel,但屢次點擊會發生屢次請求的狀況,故思索了一遍緣由。javascript

只有第一次會被釋放

如下是未修改前的代碼(一些註釋是我本身的理解,錯誤的話還望指出):

// req-intercept.js

let requestList = []; // api請求記錄

/** * 將當前請求記錄到緩存中 * @param {any} config 請求的內容 * @param {function} funcCancel 取消請求函數 */
export const add = (config, funcCancel) => {
    if (!config) {
        return false;
    }

    let req = genReq(config);
    for (let index = 0, len = requestList.length; index < len; index++) {
        if (req === requestList[index].req) {
            requestList[index].funcCancel();
            // requestList.splice(index, 1); // 把這條記錄從數組中移除
            // console.log('cancel request:', config.url);
            return;
        }
    }

    requestList.push({
        req: genReq(config),
        funcCancel
    });
};

/** * 將請求完成的對象從緩存中移除 * @param {any} config 請求對象 */
export const remove = (config) => {
    if (!config) {
        return false;
    }
    let req = genReq(config);
    for (let index = 0, len = requestList.length; index < len; index++) {
        if (req === requestList[index].req) {
            requestList.splice(index, 1); // 把這條記錄從數組中移除
            break;
        }
    }
};

// 當前請求的api是否已有記錄
export const has = (config) => {
    if (!config) {
        return false;
    }
    let req = genReq(config);
    for (let index = 0, len = requestList.length; index < len; index++) {
        if (req === requestList[index].req) {
            return true;
        }
    }
    return false;
};

/** * 生成請求記錄對象,方面對比 * @param {object} config 請求對象 */
const genReq = (config) => {
    if (!config) {
        return '';
    }
    let arrayReq = [];
    arrayReq.push(`url:${config.url},`);
    arrayReq.push(`method:${config.method},`);
    arrayReq.push(`params:${json2Form(config.params)},`);
    arrayReq.push(`header:${json2Form(config.header)}`);
    // let key = 'Method: ' + config.method + ',Url: ' + url + ',Data: ' + json2Form(data) + ', header:' + json2Form(header);
    return arrayReq.join('');
};

const json2Form = (json) => {
    var str = [];
    for (var key in json) {
        if (key !== 't') {
            str.push(encodeURIComponent(key) + '=' + encodeURIComponent(json[key]));
        }
    }
    return str.join('&');
};
複製代碼
// index.js

import axios from 'axios';
import JSONbig from 'json-bigint';
import * as reqIntercept from './req-intercept.js';
let cancelToken = axios.CancelToken;
let gOpts = {};
const JSONbigString = JSONbig({'storeAsString': true});

let axiosNew = axios.create({
    transformResponse: [function (data) {
        // return JSONbig.parse(data)
        // return JSON.parse(data);

        // 處理數據中的精度溢出的數字,將溢出的數字轉換成字符串
        return JSONbigString.parse(data);
    }]
});

function defaultHeader () {
    return {};
};
/** * 分析後端返回的錯誤信息 * @param responseHeader 後臺的相應頭對象 */
const analyzeException = (responseHeader) => {
    if (responseHeader) {
        const errCode = responseHeader['head.err.code'];
        const errMsg = responseHeader['head.err.msg'];

        return {
            errCode,
            // errMsg: errMsg ? decodeURIComponent(errMsg) : SERVICE_ERROR_STATUS[errCode]
            errMsg: errMsg ? decodeURIComponent(errMsg) : errCode
        };
    }
};

// 請求攔截器,每一個請求發出以前須要經過此函數處理一遍
axiosNew.interceptors.request.use(function (config) {
    config.cancelToken = new cancelToken((c) => {
        reqIntercept.add(config, c);
    });
    config.baseURL = gOpts.baseURL;
    // 注入自定義請求頭
    config.headers = Object.assign({}, config.headers, defaultHeader());
    return config;
}, function (error) {
    return Promise.reject(error);
});

// 響應攔截器,從服務端獲取的數據,都統一處理
axiosNew.interceptors.response.use(function (response) {
    reqIntercept.remove(response.config); // 在一個ajax響應後再執行一下取消操做,把已經完成的請求從pending中移除
    if (response.headers) {
        if (response.status === 200) {
            return response.data;
        } else {
            return Promise.reject(analyzeException(response.headers));
        }
    } else {
        // 兼容webpack-dev-server熱加載,攔截器中獲取的response只有消息體,直接返回消息體的內容
        return response;
    }
}, function (error) {
    if (!error.__CANCEL__) {
        return Promise.reject(analyzeException(error.response));
    }
    // return Promise.reject(analyzeException(error.response.headers));
});

const request = (url, params, method = 'GET', headerData = {}) => {
    method = method.toUpperCase();
    let config = {
        url: url,
        method: method,
        headers: headerData
    };
    if (method === 'POST' || method === 'PUT') {
        Object.assign(config, {
            data: params
        });
    } else {
        Object.assign(config, {
            params: Object.assign({}, params, {
                t: new Date().getTime()
            })
        });
    }
    return new Promise((resolve, reject) => {
        axiosNew(config).then((data) => {
            if (data) {
                resolve(data);
            } else {
                resolve(undefined);
            }
        }).catch(error => {
            reject(new Error(error));
        });
    });
};

export const axiosInterceptors = (target) => {
    target.prototype.responseInterceptors = (response) => {
        console.log('responseInterceptors11');
        return response;
    };

    target.prototype.fetch = (url, params, method, headerData = {}) => {
        return request(url, params, method, headerData);
    };
    /** 設置全局配置參數 */
    target.prototype.setOpts = (opts) => {
        gOpts = opts;
    };
};

複製代碼

隨後查找緣由,發如今req-intercept.js中的add方法中,若是請求的req判斷一致後,會執行cancelToken函數,但隨後也會被終止push;java

/** * 將當前請求記錄到緩存中 * @param {any} config 請求的內容 * @param {function} funcCancel 取消請求函數 */
export const add = (config, funcCancel) => {
    if (!config) {
        return false;
    }

    let req = genReq(config);
    for (let index = 0, len = requestList.length; index < len; index++) {
        if (req === requestList[index].req) {
            requestList[index].funcCancel();
            // requestList.splice(index, 1); // 把這條記錄從數組中移除
            // console.log('cancel request:', config.url);
            return;
        }
    }

    requestList.push({
        req: genReq(config),
        funcCancel
    });
};
複製代碼

隨後去除了return以後:

去除return

這樣的請求在我看來是不正確的,很是使人不適。webpack

隨後的修改結果:

/** * 將當前請求進行去重 * @param {object} req 當前請求 * @param {array} requestList api請求記錄 * @param {string} key 比對的信息key值 * @param {number} num 比對的個數 */
class repetition {
    constructor ({
        req,
        requestList,
        key,
        num
    }) {
        this.requestList = requestList;
        this.req = req;
        this.requestList.push(req);
        this.key = key;
        this.num = num || 1;
        this.cancelList = [];
    }
    cancelReq () {
        let count = 0;
        for (let i = 0; i < this.requestList.length; i++) {
            console.log(this.req[this.key], this.requestList[i][this.key]);
            if (this.req[this.key] === this.requestList[i][this.key]) {
                if (count > 0) {
                    this.requestList[i].funcCancel();
                    console.log('請求被釋放');
                    this.cancelList.push(i);
                }
                count++;
            }
        }

        for (let j = 0; j < this.cancelList.length; j++) {
            this.requestList.splice(this.cancelList[j], 1);
        }
        this.cancelList = [];
        return count;
    }
}

/** * 將當前請求記錄到緩存中 * @param {any} config 請求的內容 * @param {function} funcCancel 取消請求函數 */
export const add = (config, funcCancel) => {
    if (!config) {
        return false;
    }

    let obj = {
        req: genReq(config),
        funcCancel
    };
    let repetit = new repetition({
        req: obj,
        requestList,
        key: 'req'
    });
    repetit.cancelReq();
    // for (let index = 0, len = requestList.length; index < len; index++) {
    // if (req === requestList[index].req) {
    // console.error('-------執行funcCancel---------');
    // requestList[index].funcCancel();
    // // requestList.splice(index, 1); // 把這條記錄從數組中移除
    // // console.log('cancel request:', config.url);
    // return;
    // }
    // }
};
複製代碼

實驗的結果:ios

相同的請求

如今達到的效果是:相同的接口請求,只有等上個結果請求返回後才能進行下個請求。web

如下是所有代碼:ajax

// index.js
import axios from 'axios';
import JSONbig from 'json-bigint';
import { Message } from 'element-ui';
import { getGlobalConf } from '@/utils/grocer';
// import { APP_ID, SERVICE_ERROR_STATUS } from '@/common/constants';
import * as reqIntercept from './req-intercept.js';

let gOpts = {};
let cancelToken = axios.CancelToken;
const conf = getGlobalConf();
const JSONbigString = JSONbig({'storeAsString': true});

let axiosNew = axios.create({
    transformResponse: [function (data) {
        // return JSONbig.parse(data)
        // return JSON.parse(data);
        // 處理數據中的精度溢出的數字,將溢出的數字轉換成字符串
        return JSONbigString.parse(data);
    }]
});

function defaultHeader () {
    return {
        // 'apiVersion': '1.0'
        // 'zhsession': getZHSessionStore()
    };
};

/** * 檢查登陸狀態,若是登陸超時,跳轉到登陸頁面 * @returns 登陸超時返回 false */
const checkLogin = (resData) => {
    if (resData && typeof resData === 'string' && resData.indexOf('忘記密碼1') > -1) {
        window.location.href = '/base/login';
        return false;
    }
    return true;
};

/** * 分析後端返回的錯誤信息 * @param responseHeader 後臺的相應頭對象 */
const analyzeException = (responseData) => {
    if (responseData) {
        return {
            resultCode: responseData.code,
            // errMsg: errMsg ? decodeURIComponent(errMsg) : SERVICE_ERROR_STATUS[errCode]
            resultMsg: responseData.code
        };
    }
};

// 請求攔截器,每一個請求發出以前須要經過此函數處理一遍
axiosNew.interceptors.request.use(function (config) {
    if (config.url.indexOf('http://') < 0) {
        config.baseURL = conf.baseURL;
    }
    config.cancelToken = new cancelToken((c) => {
        console.log('已添加');
        reqIntercept.add(config, c);
    });
    console.log(3232);
    config.baseURL = gOpts.baseURL;
    // 注入自定義請求頭
    config.headers = Object.assign({}, config.headers, defaultHeader());
    return config;
}, function (error) {
    return Promise.reject(error);
});

// 響應攔截器,從服務端獲取的數據,都統一處理
axiosNew.interceptors.response.use(function (response) {
    reqIntercept.remove(response.config); // 在一個ajax響應後再執行一下取消操做,把已經完成的請求從pending中移除
    console.log(response);
    if (response.status === 200) {
        if (!checkLogin(response.data)) {
            return;
        }
        if (response.config.method.toUpperCase() !== 'GET') {
            console.log(response, 'get');
            // 兼容企業庫和圈子的數據接口
            if (response.data && (response.data.code === 200 || response.data.code === 0 || response.data.errorCode === 0 || response.data.errcode === 0 || response.data.errCode === 0 || response.data.statusCode === '200')) {
                Message({
                    title: '成功',
                    message: '操做完成',
                    type: 'success'
                });
            } else {
                if (response.data.statusCode) {
                    return response;
                } else {
                    let errMsg = response.data.msg || response.data.errmsg || '';
                    Message({
                        title: '失敗',
                        message: errMsg,
                        type: 'error'
                    });
                    throw new Error(errMsg);
                }
            }
        }

        if (response.config.method.toUpperCase() === 'POST' && response.data.statusCode) {
            console.log(response, 'post');
            return response;
        }
        console.log(response, 'no');
        return response.data;
    } else {
        Message({
            title: '失敗',
            message: '系統異常,請稍後重試。。',
            type: 'error'
        });
        // return Promise.reject(analyzeException(response));
        throw new Error('系統異常,請稍後重試。。');
        // return response.data;
    }
}, function (error) {
    // 判斷是否爲登陸超時。若是登陸超時,跳轉到登陸頁面
    if ('response' in error && error.response === undefined) {
        window.location.href = '/base/login';
        return;
    }
    if ('message' in error) {
        return;
    }
    // console.log('axiosNew.interceptors.response error', JSON.stringify(error, null, 2));
    // return Promise.reject(analyzeException(error.response.headers));
    // return Promise.reject(error);
    Message({
        title: '失敗',
        message: '後臺接口異常',
        type: 'error'
    });
    console.log(error, 'error');
    throw new Error(error);
});

const req = (url, params, method = 'GET', headerData = {}) => {
    return new Promise((resolve, reject) => {
        let config = {
            url: url,
            method: method,
            headers: headerData
        };

        if (method === 'POST' || method === 'PUT' || method === 'DELETE') {
            Object.assign(config, {
                data: params
            });
        } else {
            Object.assign(config, {
                params: Object.assign({}, params, {
                    t: new Date().getTime()
                })
            });
        };

        const arrLink = [
            'cms/lottery',
            'cms/prize',
            'cms/level'
        ];
        const hasLink = (str) => {
            let res = false;
            str += '';
            arrLink.forEach(item => {
                if (str.indexOf(item) !== -1) {
                    res = true;
                };
            });
            return res;
        };
        axiosNew(config).then((data) => {
            console.log(data, '--------------------------------');
            if (data) {
                let resData;
                if ('errorCode' in data && hasLink(config.url)) {
                    resData = data;
                } else {
                    resData = data.data || data;
                }
                resolve(resData);
            } else {
                return false;
                // resolve(undefined);
            }
        }).catch(error => {
            reject(new Error(error));
        });
    });
};

export default req;

複製代碼
// req-intercept.js
let requestList = []; // api請求記錄

/** * 將當前請求進行去重 * @param {object} req 當前請求 * @param {array} requestList api請求記錄 * @param {string} key 比對的信息key值 * @param {number} num 比對的個數 */
class repetition {
    constructor ({
        req,
        requestList,
        key,
        num
    }) {
        this.requestList = requestList;
        this.req = req;
        this.requestList.push(req);
        this.key = key;
        this.num = num || 1;
        this.cancelList = [];
    }
    cancelReq () {
        let count = 0;
        for (let i = 0; i < this.requestList.length; i++) {
            console.log(this.req[this.key], this.requestList[i][this.key]);
            if (this.req[this.key] === this.requestList[i][this.key]) {
                if (count > 0) {
                    this.requestList[i].funcCancel();
                    console.log('請求被釋放');
                    this.cancelList.push(i);
                }
                count++;
            }
        }

        for (let j = 0; j < this.cancelList.length; j++) {
            this.requestList.splice(this.cancelList[j], 1);
        }
        this.cancelList = [];
        return count;
    }
}

/** * 將當前請求記錄到緩存中 * @param {any} config 請求的內容 * @param {function} funcCancel 取消請求函數 */
export const add = (config, funcCancel) => {
    if (!config) {
        return false;
    }

    let obj = {
        req: genReq(config),
        funcCancel
    };
    let repetit = new repetition({
        req: obj,
        requestList,
        key: 'req'
    });
    repetit.cancelReq();
    // for (let index = 0, len = requestList.length; index < len; index++) {
    // if (req === requestList[index].req) {
    // console.error('-------執行funcCancel---------');
    // requestList[index].funcCancel();
    // // requestList.splice(index, 1); // 把這條記錄從數組中移除
    // // console.log('cancel request:', config.url);
    // return;
    // }
    // }
};

/** * 將請求完成的對象從緩存中移除 * @param {any} config 請求對象 */
export const remove = (config) => {
    if (!config) {
        return false;
    }
    let req = genReq(config);
    for (let index = 0, len = requestList.length; index < len; index++) {
        if (req === requestList[index].req) {
            console.log('-- removed---');
            requestList.splice(index, 1); // 把這條記錄從數組中移除
            break;
        }
    }
};

// 當前請求的api是否已有記錄
export const has = (config) => {
    if (!config) {
        return false;
    }
    let req = genReq(config);
    for (let index = 0, len = requestList.length; index < len; index++) {
        if (req === requestList[index].req) {
            return true;
        }
    }
    return false;
};

/** * 生成請求記錄對象,方面對比 * @param {object} config 請求對象 */
const genReq = (config) => {
    if (!config) {
        return '';
    }
    let arrayReq = [];
    arrayReq.push(`url:${config.url},`);
    arrayReq.push(`method:${config.method},`);
    arrayReq.push(`params:${json2Form(config.params)},`);
    arrayReq.push(`header:${json2Form(config.header)}`);
    // let key = 'Method: ' + config.method + ',Url: ' + url + ',Data: ' + json2Form(data) + ', header:' + json2Form(header);
    return arrayReq.join('');
};

const json2Form = (json) => {
    var str = [];
    for (var key in json) {
        if (key !== 't') {
            str.push(encodeURIComponent(key) + '=' + encodeURIComponent(json[key]));
        }
    }
    return str.join('&');
};
複製代碼
相關文章
相關標籤/搜索