preface

Website performance optimization is a necessary process belonging to every website, and the most direct and simple is HTTP request optimization, including reducing the number of HTTP requests, speed up HTTP request. In this article, we will start with the number of HTTP requests and filter out all useless and invalid requests.

start

Based on the AXIOS open source library, this article will encapsulate HTTP requests, including two small optimizations: caching requests and filtering repeated requests.

The first step

Create an HTTP-helper. js file, which will encapsulate the above functions based on AXIOS.

import axios from 'axios';

const http = axios.create();

export default http
Copy the code

This is a simple export of an axiOS instance for your project to use

Added request caching capabilities

Then has the function of the cache, must be to define a cache hit, I define the cache hit refers to: HTTP request the url of the same, the same type of request and request parameters, above all have the same situation, just as a cache allows hit, according to the cache expiration time, determine whether to get the latest data, or from the cache. Here is the process:

  1. Initiates a request and sets whether or not the request is cached and for how long
  2. Axios request interception determines whether the request is cached. Yes? Check whether the cache hits and expires. No? The request continues
  3. Axios response intercepts to determine whether the result of the request is cached. Yes? Caches data and sets the key value and expiration time

For the above process, there are a few things to confirm:

  1. In AXIos, a cancleToken can be set for each request. When the request cancellation method is called, the request terminates and the terminated message passesrejectPass back to the request method.
  2. When the cache hits, andThe cached data is returned to the request method via resolve() rather than retrieved in reject

The specific code might look like this:

// http-helper.js
import axios from 'axios';

const http = axios.create();

http.interceptors.request.use((config) = > {
    /** * Generates a cancleToken */ for each request
    const source = axios.CancelToken.source();
    config.cancelToken = source.token;
    /** * Try to get cached data */
    const data = storage.get(cryptoHelper.encrypt(
        config.url + JSON.stringify(config.data) + (config.method || ' ')));/** * Check whether the cache is hit and not expired */
    if (data && (Date.now() <= data.exppries)) {
        console.log(` interface:${config.url}Cache hit --The ${Date.now()} -- ${data.exppries}`);
        /** * The cached data is passed back to the requesting method */ via cancle
        source.cancel(JSON.stringify({
            type: CANCELTTYPE.CACHE,
            data: data.data,
        }));
    }
    return config;
});

http.interceptors.response.use((res) = > {
    if (res.data && res.data.type === 0) {
        if (res.config.data) {
            /** * gets the request body argument */
            const dataParse = JSON.parse(res.config.data);
            if (dataParse.cache) {
                if(! dataParse.cacheTime) { dataParse.cacheTime =1000 * 60 * 3;
                }
                /** * encryption * cache */
                storage.set(cryptoHelper.encrypt(res.config.url + res.config.data + (res.config.method || ' ')), {
                    data: res.data.data, // Response body data
                    exppries: Date.now() + dataParse.cacheTime, // Set the expiration time
                });
                console.log(` interface:${res.config.url}Set the cache, cache time:${dataParse.cacheTime}`); }}return res.data.data;
    } else {
        return Promise.reject('Interface error! '); }});/** * encapsulates get and POST requests * integrated interface cache expiration mechanism * Cache expiration rerequests the latest data and updates the cache ** Data is stored in localstorage * {* cache: true * cacheTime: 1000 * 60 * 3 -- the default cache is 3 minutes *} */
const httpHelper = {
    get(url, params) {
        return new Promise((resolve, reject) = > {
            http.get(url, params).then(async (res) => {
                resolve(res);
            }).catch((error) = > {
                if (axios.isCancel(error)) {
                    const cancle = JSON.parse(error.message);
                    if (cancle.type === CANCELTTYPE.REPEAT) {
                        return resolve([]);
                    } else {
                        returnresolve(cancle.data); }}else {
                    returnreject(error); }}); }); }, post(url: string,params: any) {
        return new Promise((resolve, reject) = > {
            http.post(url, params).then(async (res) => {
                resolve(res);
            }).catch((error: AxiosError) = > {
                if (axios.isCancel(error)) {
                    const cancle = JSON.parse(error.message);
                    if (cancle.type === CANCELTTYPE.REPEAT) {
                        return resolve(null);
                    } else {
                        returnresolve(cancle.data); }}else {
                    returnreject(error); }}); }); }};export default httpHelper
Copy the code

There are some things that are not explained in the above code:

  1. Among themstorageIs its own encapsulation of the cache data class, can have.get、.setWait for a method,cryptoHelperIs encapsulatedMD5 encryption library, mainlyMD5 is used to encrypt the request URL, request data, and request typeTo obtain the data in the cache through the encrypted key (because the string after concatenation is too long, MD5 encryption will be much shorter)
  2. Why encapsulate one separatelyhttpHelperBecause theaxios.CancelToken.source().cancle(***)Reject, available only in reject,In order to cache hits, the correct data can still be retrieved in then, you need to deal with this case separately.

Added the function of filtering repeated requests

Rule: The latest request is the main one, that is, the latest repeated request. The previous repeated request is interrupted as follows:

  1. The initiating
  2. Axios request interception, check whether the same request exists in the request list array, yes? Terminate all previous repeated requests, yes? Adding the current request to the request array will eventually continue to request
  3. Axios responds to the interceptor by removing the current request from the request array

The specific code is as follows:

// http-helper.js
import axios from 'axios';

const http = axios.create();

const pendingRequests = [];

http.interceptors.request.use((config) = > {
    /** * Generates a cancleToken */ for each request
    const source = axios.CancelToken.source();
    config.cancelToken = source.token;
    / /... Omit some code
    /** * Repeat request * same URL, same request type repeat request */
    const md5Key = cryptoHelper.encrypt(config.url + (config.method || ' '));
    /** * Cancel all previous repeated and incomplete requests */
    const hits = pendingRequests.filter((item) = > item.md5Key === md5Key);
    if (hits.length > 0) {
        hits.forEach((item) = > item.source.cancel(JSON.stringify({
            type: CANCELTTYPE.REPEAT,
            data: 'Repeat request to cancel',}))); }/** * adds the current request to the request pair column */
    pendingRequests.push({
        md5Key,
        source,
    });
    return config;
});

http.interceptors.response.use((res) = > {
    /** * Removes the completed request from the request queue */ regardless of whether the request was successful or not
    // Get the encrypted string using the same encryption method (MD5)
    const md5Key = cryptoHelper.encrypt(res.config.url + (res.config.method || ' '));
    const index = pendingRequests.findIndex((item) = > item.md5Key === md5Key);
    if (index > - 1) {
        pendingRequests.splice(index, 1);
    }
    / /... Omit some code
});

/ /... Omit some code
Copy the code

The logic is simple, maintaining a list of requests through an array

End product

Cancle: CANCELTTYPE: CANCELTTYPE: CANCELTTYPE: CANCELTTYPE: CANCELTTYPE: CANCELTTYPE: CANCELTTYPE: CANCELTTYPE **http-helper.ts **

import axios, {CancelTokenSource, AxiosResponse, AxiosRequestConfig, AxiosError} from 'axios';
import Storage from './storage-helper';
import CryptoHelper from './cryptoJs-helper';

const CANCELTTYPE = {
    CACHE: 1,
    REPEAT: 2,
};

interface ICancel {
    data: any;
    type: number;
}

interface Request {
    md5Key: string;
    source: CancelTokenSource;
}
const pendingRequests: Request[] = [];

const http = axios.create();
const storage = new Storage();
const cryptoHelper = new CryptoHelper('cacheKey'); HTTP. Interceptors. Request. Use ((config: AxiosRequestConfig) = > {/ * * * for each request to generate a cancleToken * / constsource= axios.CancelToken.source(); config.cancelToken = source.token; /** * Const data = storage.get(cryptohelper.encrypt (config.url + json.stringify (config.data) +) (config.method ||' ')));if(data && (date.now () <= data.exppries)) {console.log(' interface:${config.url}Cache hit --${Date.now()} -- ${data.exppries}`);
        source.cancel(JSON.stringify({
            type: CANCELTTYPE.CACHE, data: data.data, })); } / repeat * * * * with the url request judgment, with the request type judged to be repeated request * will be subject to the latest request * / const md5Key = cryptoHelper. Encrypt (config. Url + (config) method | |' ')); /** * const hits = pendingRequests. Filter ((item) => item.md5key === md5Key);if (hits.length > 0) {
        hits.forEach((item) => item.source.cancel(JSON.stringify({
            type: CANCELTTYPE.REPEAT,
            data: 'Repeat request to cancel',}))); } ** * Add the current request to the request pair */ pendingRequests. Push ({md5Key,source});returnconfig; }); http.interceptors.response.use((res: AxiosResponse) => {/** * whether the request succeeds or not, */ // Get the encrypted string const md5Key = cryptohelper. encrypt(res.config.url + (res.config.method) using the same encryption method (MD5) ||' '));
    const index = pendingRequests.findIndex((item) => item.md5Key === md5Key);
    if (index > -1) {
        pendingRequests.splice(index, 1);
    }
    if (res.data && res.data.type === 0) {
        if (res.config.data) {
            const dataParse = JSON.parse(res.config.data);
            if (dataParse.cache) {
                if(! dataParse.cacheTime) { dataParse.cacheTime = 1000 * 60 * 3; } storage.set(cryptoHelper.encrypt(res.config.url + res.config.data + (res.config.method ||' ')), { data: res.data.data, exppries: Date.now() + dataParse.cacheTime, }); The console. The log (` interface:${res.config.url}Set the cache, cache time:${dataParse.cacheTime}`); }}return res.data.data;
    } else {
        return Promise.reject('Interface error! '); }}); /** * Encapsulates get and POST requests ** integrated interface cache expiration mechanism * Cache expiration rerequests the latest data and updates the cache ** Data is stored in localstorage * {* cache:true* cacheTime: 1000 * 60 * 3 -- default cache for 3 minutes *} */ const httpHelper = {get(url: string, params: any) {return new Promise((resolve, reject) => {
            http.get(url, params).then(async (res: AxiosResponse) => {
                resolve(res);
            }).catch((error: AxiosError) => {
                if (axios.isCancel(error)) {
                    const cancle: ICancel = JSON.parse(error.message);
                    if (cancle.type === CANCELTTYPE.REPEAT) {
                        return resolve([]);
                    } else {
                        returnresolve(cancle.data); }}else {
                    returnreject(error); }}); }); }, post(url: string, params: any) {return new Promise((resolve, reject) => {
            http.post(url, params).then(async (res: AxiosResponse) => {
                resolve(res);
            }).catch((error: AxiosError) => {
                if (axios.isCancel(error)) {
                    const cancle: ICancel = JSON.parse(error.message);
                    if (cancle.type === CANCELTTYPE.REPEAT) {
                        return resolve(null);
                    } else {
                        returnresolve(cancle.data); }}else {
                    returnreject(error); }}); }); }};export default httpHelper;

Copy the code

cryptoJs-helper.ts

import cryptoJs from 'crypto-js'; class CryptoHelper { public key: string; Constructor (key: string) {/** * if you need a private key, pass */ this.key = key on instantiation; } / encryption * * * * @ param word * / public encrypt (word: string | undefined) : string {if(! word) {return ' ';
        }
        const encrypted = cryptoJs.MD5(word);
        returnencrypted.toString(); }}export default CryptoHelper;

Copy the code

storage-helper.ts

class Storage {
    public get(key: string | undefined) {
        if(! key) {return; }
        const text = localStorage.getItem(key);
        try {
            if (text) {
                return JSON.parse(text);
            } else {
                localStorage.removeItem(key);
                return null;
            }
        } catch {
            localStorage.removeItem(key);
            return null;
        }
    }
    public set(key: string | undefined, data: any) {
        if(! key) {return;
        }
        localStorage.setItem(key, JSON.stringify(data));
    }
    public remove(key: string | undefined) {
        if(! key) {return;
        }
        localStorage.removeItem(key); }}export default Storage;

Copy the code

If you have any questions, please correct them. Thank you!