ES6 proxy and reflection implement proxy method proxy

Personal website: https://aijianli.site/ You can create a resume online for free and provide PDF download, which is convenient and fast.

vqa (view query api) is a back-end query component, which is convenient for writing SQL queries and can reduce the need to add business code to the back-end code. The way the front end calls the interface is relatively complicated. Therefore, ES6 reflection and proxy are used to transform the calling method to achieve convenient calling.

1. Original vqa interface calling method

function findStatsTable() {<!-- -->
    let params = {<!-- -->
        method: "stats-service-stats-table.findTable",
        datas: {<!-- -->
            st_id: "1234567890",
        }
    };
    this.axios.post("/stats/vqa-query", params).then(res => {<!-- -->
        console.log(res)
    })
}

Problems:

  • The parameter params are too complex, and there are many vqas in the same module. The prefix (stats-service-stats-table) in their params.method is the same. Only the method name is different. The same content must be copied for each query. Later It is difficult to maintain if changes are made
  • The interface address (/stats/vqa-query) of the same service is the same, and the same string must be written every time, which is too repetitive.

2. Improved vqa calling method

function findStatsTable() {<!-- -->
    let params = {<!-- -->
         st_id: "1234567890",
    };
    this.$statsTable.findTable(params).then(res => {<!-- -->
        console.log(res)
    })
}

Improved advantages:

  • The call is simple, you only need to pay attention to the request parameters,
  • Easy to maintain, strings such as stats-service-stats-table, /stats/vqa-query will not appear repeatedly in different places

3. Implementation code

3.1 Class diagram structure design

3.2 Specific implementation

Among them, the content of the _sendVqaRequest method in ParentInterface is as follows. Here is the encapsulated original vqa method.

class ParentInterface {<!-- -->
    /*......*/

    /**
     * Item vqa interface sends request. Note that vqa requests are all post requests.
     * @param {Object} params corresponds to the parameters required in the datas of the query method
     * @param {String} method The vqa method queried, no method prefix is required, only the method name is required
     * @returns
     */
    _sendVqaRequest(params, method) {<!-- -->
        params = this._getParams(params, method);
        return this[axiosSym].post(this[vqaUrlSym], params).then(res => {<!-- -->
            return res.data.content;
        })
    }
     /*......*/
}

In order to uniformly call the _sendVqaRequest method when calling methods that do not exist in the class, you need to use ES6 reflection and proxy. This is the key to realizing this function. The specific implementation is as follows:

 /**
 * Initialize all interfaces
 * @param {*} app Vue
 * @param {*} axios request instance
 */
function initInterfaces(app, axios) {<!-- -->

    const statsTable = createInterfaceProxy(new StatsTable(axios));

    const interfaces = {<!-- -->
        statsTable
    };

    for (let interfac in interfaces) {<!-- -->
        app.prototype[`$${<!-- -->interfac}`] = interfaces[interfac];
    }
}

core code

/**
 * Create an interface proxy to process vqa queries. When calling vqa method, get the name of the calling method and call the background vqa
 * @param {Object} target The object being proxied, here is the instance of the interface
 * @returns returns a proxy object
 */
function createInterfaceProxy(target) {<!-- -->
    let hander = {<!-- -->
        get(target, key) {<!-- -->
            let vqaMethod = Reflect.get(target, key);
            if (vqaMethod) {<!-- -->
                return vqaMethod;
            }
            vqaMethod = Reflect.get(target, "_sendVqaRequest"); // Hard-coded, all vqa queries call the _sendVqaRequest method
            let vqaProxy = new Proxy(vqaMethod, {<!-- -->
                apply(funtarget, funthis, args) {<!-- -->
                    let params = [...args, key]
                    return Reflect.apply(funtarget, funthis, params)
                }
            })
            return vqaProxy;
        }
    }
    return new Proxy(target, hander)
}

The following code implements the proxy for the target object through ES6 proxy and reflection.

By proxying the get method of the target, when the target is called to call its own method, if the called method originally exists in the target, the method will be returned directly. If it does not exist, a proxy object is returned. This proxy object represents a public method in the target. This achieves that when a method that does not exist in an object is called, a specific public method will be called uniformly.

3.3 Partial code display

3.3.1 ParentInterface class

/**
 * The parent class of all interface classes, providing methods common to all interfaces
 * Note: Each specific class that inherits this parent interface must add the following configuration to the config object in the config file.
 *
 * type: {
 * interfacePrefix: xxx, //Prefix of the background interface
 * }
 *
 * For example, the interface class configuration of the snapshot is as follows
 * StatsSnapshot: {
        interfacePrefix: "/stats/snapshots",
    }
 *
 * @author hebing
 * @date 2021/03/09
 */
import config from "./config.js";

//Use symbol as key to avoid changing these properties when used externally, causing errors in subsequent requests.
let axiosSym = Symbol("axios");
let vqaUrlSym = Symbol("vqaUrl");
let vqaPrefixSym = Symbol("vqaPrefix");
let interfacePrefixSym = Symbol("interfacePrefix");

class ParentInterface {<!-- -->

    constructor(className, axios) {<!-- -->
        //The class name of the implementation class is used to retrieve the vqaUrl, vqaPrefix and interfacePrefix of the specific class from the configuration file based on the instance object of the created class.
        this[axiosSym] = axios;
        let implClass = className;

        let configs = config[implClass];

        if (!configs) throw new Error(`Interface class [${<!-- -->className}] is missing a configuration file!`)

        this[vqaUrlSym] = configs.vqaUrl;
        this[vqaPrefixSym] = configs.vqaPrefix;
        this[interfacePrefixSym] = configs.interfacePrefix;

    }
    /**
     * Correct query parameters of vqa
     * @param {Object} params Parameters required to query vqa
     */
    _fixParams(params) {<!-- -->
        params.method = this[vqaPrefixSym] + params.method;
    }

    /**
     * Get request parameters
     * @param {Object} datas request parameters
     * @param {String} method request method
     * @returns Returns the parameters required to query vqa
     */
    _getParams(datas, method) {<!-- -->
        let params = {<!-- -->
            method: method,
            datas: datas
        }
        this._fixParams(params);
        return params;
    }

    /**
     * Item vqa interface sends request. Note that vqa requests are all post requests.
     * @param {Object} params corresponds to the parameters required in the datas of the query method
     * @param {String} method The vqa method queried, no method prefix is required, only the method name is required
     * @returns
     */
    _sendVqaRequest(params, method) {<!-- -->
        params = this._getParams(params, method);
        return this[axiosSym].post(this[vqaUrlSym], params).then(res => {<!-- -->
            return res.data.content;
        })
    }

    /**
     * Correct the request path, used in the background interface
     * @param {*} method The part after the prefix in the config configuration file
     * @param {*} fullUrl specifies whether method is a full path
     * @returns
     */
    _fixUrl(method, fullUrl) {<!-- -->
        let realUrl = this[interfacePrefixSym];
        if (method) {<!-- -->
            if (fullUrl) {<!-- -->
                realUrl = method;
            } else if (method.startsWith("/")) {<!-- -->
                realUrl + = method;
            } else {<!-- -->
                realUrl + = ("/" + method);
            }
        }
        return realUrl;
    }

    /**
     * Send a request to the backend interface
     * @param {Object} params corresponding to the parameters of the background interface
     * @param {String} method The part after the prefix in the config configuration file
     * @param {Boolean} fullUrl specifies whether method is a full path
     * @returns
     */
    _sendPostRequest(params, method, fullUrl) {<!-- -->
        let realUrl = this._fixUrl(method, fullUrl);
        return this[axiosSym].post(realUrl, params, {<!-- -->
            loading: params.loading
        }).then(res => {<!-- -->
            return res.data.content;
        })
    }

    /**
     * Send a request to the backend interface
     * @param {Object} params corresponding to the parameters of the background interface
     * @param {String} method The part after the prefix in the config configuration file
     * @param {Boolean} fullUrl specifies whether method is a full path
     * @returns
     */
    _sendGetRequest(params, method, fullUrl) {<!-- -->
        let realUrl = this._fixUrl(method, fullUrl);
        return this[axiosSym].get(realUrl, {<!-- -->
            params
        }).then(res => {<!-- -->
            return res.data.content;
        })
    }

   
}

export default ParentInterface;

3.3.2 index.js

import StatsTable from "./stats/StatsTable.js";

/**
 * Create an interface proxy to process vqa queries. When calling vqa method, get the name of the calling method and call the background vqa
 * @param {Object} target The object being proxied, here is the instance of the interface
 * @returns returns a proxy object
 */
function createInterfaceProxy(target) {<!-- -->
    let hander = {<!-- -->
        get(target, key) {<!-- -->
            let vqaMethod = Reflect.get(target, key);
            if (vqaMethod) {<!-- -->
                return vqaMethod;
            }
            //Written to death, all vqa queries call the _sendVqaRequest method
            vqaMethod = Reflect.get(target, "_sendVqaRequest");
            let vqaProxy = new Proxy(vqaMethod, {<!-- -->
                apply(funtarget, funthis, args) {<!-- -->
                    let params = [...args, key]
                    return Reflect.apply(funtarget, funthis, params)
                }
            })
            return vqaProxy;
        }
    }
    return new Proxy(target, hander)
}

/**
 * Initialize all interfaces
 * @param {*} app Vue
 * @param {*} axios request instance
 */
function initInterfaces(app, axios) {<!-- -->

    const statsTable = createInterfaceProxy(new StatsTable(axios));

    const interfaces = {<!-- -->
        statsTable
    };

    for (let interfac in interfaces) {<!-- -->
        app.prototype[`$${<!-- -->interfac}`] = interfaces[interfac];
    }
}

export default initInterfaces;