如何更有效的在中大型项目中管理ajax请求

前言

在中大型的前端应用里面项目里面通常包夹了大量的ajax请求,通常情况这些请求夹杂在业务代码里面,拓展维护的成本比较大。

如要改整个项目的url前缀(后端把接口路径统一加个assets或者http换成https),或者要改请求的类型get换post,亦或者统一加个CSRFToken,我们该当如何,手动一个个把自己一头扎进业务代码里面去修改吗?

此时不妨试试用一个js去统一管理这些ajax请求。

分析

以下是我们使用ajax请求的方式
如何更有效的在中大型项目中管理ajax请求

调用请求的时候拼装url,在较多ajax请求的时候面,维护拓展就变得非常的艰难
本文章想法是把url抽象成不同类型的参数

将url分为三类:

  • 公共型参数: 每个url都共用的部分 如http类型,url前缀,mockurl前缀,token等

  • 配置型参数: 每个url都不一样的,但可以提前预知的部分,如uri地址,ajax请求方式(get/post)

  • 业务型参数: 具体进业务场景方能算出来的参数 如test=xxx(xxx的值只有程序运行的时候才知道值)

如何更有效的在中大型项目中管理ajax请求

公共型参数和配置型参数可以在项目初始化的时候通过逻辑控制并存起来,
业务型参数在具体调用的时候在具体的业务场景获取。
在通过统一的ajax请求获取资源

流程图

如何更有效的在中大型项目中管理ajax请求

统一的ajax请求,可以在失败之后,统一抛出异常,成功之后统一改变获取数据的结构等等好处

代码实现

简单工厂存储url参数对象

简单工程创建 参数实例,并将实例对象存储在context内部

    (function (root,$) {
    //简单工厂生成项目统一的ajax参数对象
    var DBFactory = {
        __: {},
        //可有业务代码设置mockUrl,prefixUrl等
        set: function (key, value) {
            this.__[key] = value;
        },
        get: function (key) {
            return this.__[key];
        },
        create: function (name, methods) {
            // 禁止创建重名的DB实例
            if (this.context[name]) {
                console.warn('DB: "' + name + '" is existed! ');
                return;
            }
            return this.context[name] = new DB(name, methods);
        },
        // 存储db实例
        context: {
            
        }
    };


    //DB构造函数 负责构造ajax参数对象
    function DB(DBName, methods) {
        var t = this;
        t.cache = {};
        $.each(methods, function (method, config) {
            if (typeof config === 'function') {
                t[method] = config;
                return;
            }

            t[method] = function (query) {
                var cfg = {};

                cfg.method = method;

                cfg.DBName = DBName;

                cfg.mockUrl = config.mockUrl;

                // 如果设置了`mock`代理
                if (cfg.mockUrl && typeof DBFactory.__.mockProxy === 'function') {
                    cfg.mockUrl = DBFactory.__.mockProxy(cfg.mockUrl);
                }
                //合并参数
                cfg.query = $.extend({}, config.query || {}, query || {});
                //判断是否mock
                cfg.isMock = config.url ? false : true;
                //url前缀供getUrl计算url
                t.urlPrefix = DBFactory.get('urlPrefix') || '';
                //url
                cfg.url = cfg.isMock ? cfg.mockUrl : (t.getUrl(config.url) || cfg.mockUrl);
                // 是否是全局只获取一次
                cfg.once = typeof config.once === 'boolean' ? config.once : false;
                // 数据缓存,如果`once`设置为true,则在第二次请求的时候直接返回改缓存数据。
                t.cache[method] = t.cache[method] || null;

                cfg.jsonp = config.jsonp || false;

                cfg.type = config.type || 'POST';
                return request(cfg, t);
            };
        });
    }

    /**
     * 获取正式接口的完整`url`
     * 如果通过`DB.set('urlPrefix', 'https://xxx')`设置了全局`url`的前缀,则执行补全
     */
    DB.prototype.getUrl=function (url) {
        if (this.urlPrefix && url.indexOf('http') !== 0 && url.indexOf('//') !== 0) {
            return this.urlPrefix + url;
        } else {
            return url;
        }
    };

    /**
     *
     * @param cfg  属性对象提供给ajax修改请求属性
     * @param db   db提供缓存
     * @returns  ajax promise
     */
    function request(cfg, db) {
        var defer = $.Deferred();
        if (cfg.once && db.cache[cfg.method]) {
            defer.resolve(db.cache[cfg.method]);
        } else {
            var ajaxOptions = {
                url: cfg.url,
                data: cfg.query,
                success: function (resp) {
                    //cfg.once && (db.cache[cfg.method] = resp);
                    defer.resolve(resp);
                },
                error: function (error) {
                    defer.reject({
                        fail:true,
                        msg:  error
                    });
                }
            };

            if (cfg.jsonp === true) {
                ajaxOptions.dataType = 'jsonp';
            } else {
                ajaxOptions.dataType = 'json';
                ajaxOptions.type = cfg.type;
            }

            $.ajax(ajaxOptions);
        }
        return defer.promise();
    };
    root.DBFactory = DBFactory;
})(this,$);

配置参数

  • db.js 负责注册ajax请求,并全局的设置url前缀,是否启用mockurl,对不同请求配置不同的请求方式,是否jsonp等

(function (DBF) {
var isOnline = false;
// 设置全局的`url`前缀
    var urlPrefixForLocal = location.protocol + '//' + location.host+'/';
    if (!isOnline) {
        DBF.set('urlPrefix', urlPrefixForLocal);
    } else {
        DBF.set('urlPrefix', '/trs');
    }

// MockProxy mock 数据服务器(node/php代理)代理,以跨过同源检测
    DBF.set('mockProxy', function (mockUrl) {
        return '/mock?url=' + mockUrl
    });

    DBF.create('Test', {
        get: {
            type: 'GET',
            // jsonp: true,
            url: 'example/ajax-data/data.json'
            // mockUrl: urlPrefixForLocal + '/ajax-data/data.json',
            //once: true
        }
    });

    DBF.create('GetPersonInfo', {
        get: {
            type: 'GET',
            url: 'getMySimpleInfo.json',
            once: true
        }
    });

    window.DB = DBF.context;
})(window.DBFactory);

开始测试

加上请求的资源

如何更有效的在中大型项目中管理ajax请求

调用代码

  • ajax的真正调用者,通过DB.xxx 拿到参数对象后以get的形式触发实际的ajax请求,并附带业务代码里面的参数。

            $(document).ready(
            function () {
                var DB = window.DB;
                var promise1 = $.when(DB.Test.get({test: true}));
                var promise2 = $.when(DB.GetPersonInfo.get());
                promise1.then(
                        function (res) {
                            console.log(res)
                            $('#promise1').html('promise1:'+res.data);
                        }, function (error) {
                            console.log(error);
                        });

                promise2.then(
                        function (res) {
                            console.log(res)
                        }, function (error) {
                            $('#promise2').html('promise2:'+error.msg.responseText);
                        });
            }
    )
   

运行结果

promise1 拿data.json 返回成功
promise2 拿getMySimpleInfo.json 资源未获取到,返回失败

如何更有效的在中大型项目中管理ajax请求

本篇文章是以jquery形式展示的,但稍事修改也是可以在backbone,react等框架内使用。
测试代码及源码下载在https://github.com/laughing-p...

归纳

本篇文章是通过对ajax请求调用前,调用后的统一处理来更有效的控制这些请求,类似于面向切面的思想。