AboutFE icon indicating copy to clipboard operation
AboutFE copied to clipboard

3、JS动态串行、并行加载脚本, 请求合并

Open CodingMeUp opened this issue 7 years ago • 1 comments

我们可以动态的创建



<script type="text/javascript">
/**
 * 串联加载指定的脚本
 * 串联加载[异步]逐个加载,每个加载完成后加载下一个
 * 全部加载完成后执行回调
 * @param array|string 指定的脚本们
 * @param function 成功后回调的函数
 * @return array 所有生成的脚本元素对象数组
 */

    function seriesLoadScripts(scripts,callback) {
      if(typeof(scripts) != "object") var scripts = [scripts];
      var HEAD = document.getElementsByTagName("head").item(0) || document.documentElement;
      var s = new Array(), last = scripts.length - 1, recursiveLoad = function(i) { //递归
        s[i] = document.createElement("script");
        s[i].setAttribute("type","text/javascript");
        s[i].onload = s[i].onreadystatechange = function() { //Attach handlers for all browsers
          if(!/*@cc_on!@*/0 || this.readyState == "loaded" || this.readyState == "complete") {
            this.onload = this.onreadystatechange = null; this.parentNode.removeChild(this);
            if(i != last) recursiveLoad(i + 1); else if(typeof(callback) == "function") callback();
          }
        }
        s[i].setAttribute("src",scripts[i]);
        HEAD.appendChild(s[i]);
      };
      recursiveLoad(0);
    }


    /**
     * 并联加载指定的脚本
     * 并联加载[同步]同时加载,不管上个是否加载完成,直接加载全部
     * 全部加载完成后执行回调
     * @param array|string 指定的脚本们
     * @param function 成功后回调的函数
     * @return array 所有生成的脚本元素对象数组
     */

    function parallelLoadScripts(scripts,callback) {
      if(typeof(scripts) != "object") var scripts = [scripts];
      var HEAD = document.getElementsByTagName("head").item(0) || document.documentElement, s = new Array(), loaded = 0;
      for(var i=0; i<scripts.length; i++) {
        s[i] = document.createElement("script");
        s[i].setAttribute("type","text/javascript");
        s[i].onload = s[i].onreadystatechange = function() { //Attach handlers for all browsers
          if(!/*@cc_on!@*/0 || this.readyState == "loaded" || this.readyState == "complete") {
            loaded++;
            this.onload = this.onreadystatechange = null; this.parentNode.removeChild(this);
            if(loaded == scripts.length && typeof(callback) == "function") callback();
          }
        };
        s[i].setAttribute("src",scripts[i]);
        HEAD.appendChild(s[i]);
      }
    }

    var scripts = [
        "http://www.jq22.com/js/jquery.min.js",
        "http://www.jq22.com/js/bootstrap.min.js"
      ];

      //这两个文件分别是 jQuery 1.4.的库文件和 jQuery Debug 插件
      //然后你可以使用下面的方法调用并在成功后执行回调了。

      seriesLoadScripts(scripts,function(){

        /*
        debug = new $.debug({
          posTo : { x:'right', y:'bottom' },
          width: '480px',
          height: '50%',
          itemDivider : '<hr>',
          listDOM : 'all'
        });

        */
        alert('脚本加载完成啦');
      });

</script>

CodingMeUp avatar Nov 15 '17 06:11 CodingMeUp

前端简易版接口聚合模块 request-combo

场景:

  • 一个支持参数合并的接口,在组件化或其他场景下调用了不同参数的相同的接口,这时把这些调用合并成一个或多个接口再请求。
  • 避免发起相同的请求,某些情况下发起了相同的请求,经收集处理后,实际只发起一个请求。但是不同的发起端的callback 都能得到处理。

设计:

  • 要知道接口的基本信息,包括但不限于 url、params、callback…
  • 既然要聚合,那么得有一个收集接口的队列
  • 每个接口的队列要有状态,当一个新接口到来时,该接口的队列可能还没创建,可能正在收集,可能刚发完请求。
  • 要有接口队列发起请求的条件,收集时间够了或者收集长度够了…
  • 有缓存机制,已获取的数据暂时缓存起来
import axios from 'axios';

interface ApiData {
    url: string;
    pack: Function;
    unpack: Function;
    maxComboNum?: number;
    requestMethod?: string;
}

/**
 * status: number
 * 0:init
 * 1:pending
 * 2:done
 * 
 * request
 * The api must be the same as axios
 */
const dataCache: object = {};
const busData: object = {};

export const requestCombo = (apiData: ApiData, params: object, callback?: Function, request = axios, collectTime = 100, isCombo = true, errorHandle?: Function) => {
    const { url, requestMethod = 'get', maxComboNum = 10, pack, unpack } = apiData;

    const method: string = requestMethod.toLocaleLowerCase();
    const cacheKey = `${url}_${method}_${JSON.stringify(params)}`;
    const busKey = `${url}_${method}`;

    if (!url) return;

    const sendRequest = async () => {
        clearTimeout(busData[busKey].timer);
        const paramList = busData[busKey].paramList;
        const paramObject = pack(paramList);

        busData[busKey] = null;
        try {
            const result = await applyRequest(url, paramObject, method, request);
            // 拆解,拆完要对应回去,因此要用 param 做key
            const obj = unpack(result, paramList) || {};
            Object.keys(obj).forEach((key) => {
                const dataNode = dataCache[cacheKey];
                if (!dataNode) {
                    errorHandle ? errorHandle('Data Unpack Error') : console.log('Data Unpack Error');
                } else {
                    dataNode.data = obj[key];
                    dataNode.status = 2;
                    dataNode.cbs.forEach((cb: Function) => {
                        cb(obj[key]);
                    });
                }
            });
        } catch (ex) {
            if (errorHandle) {
                errorHandle(ex);
                return;
            }
            throw ex;
        }
    };

    return new Promise((resolve, reject) => {
        if (!callback) callback = () => { }; //预处理接口返回数据
        const _callback = callback;
        callback = (json: any) => {
            const raw = _callback(json);
            if (raw && typeof raw.then === 'function') {//认为是Promise
                raw.then((data: any) => {
                    resolve(data);
                }).catch((err: any) => { reject(err); }); //终结的promise链必须捕获错误,否则丢失错误链
            } else {
                resolve(raw);
            }
        };

        if (dataCache[cacheKey]) {
            if (dataCache[cacheKey].status === 1) {
                dataCache[cacheKey].cbs.push(callback);
            } else if (dataCache[cacheKey].status === 2) {
                callback(dataCache[cacheKey].data);
            }
        } else {
            dataCache[cacheKey] = {
                status: 1,
                cbs: [],
                data: {}
            };
            if (!isCombo) {
                applyRequest(url, params, requestMethod, request).then((data: object) => {
                    dataCache[cacheKey].status = 2;
                    dataCache[cacheKey].data = data;
                    dataCache[cacheKey].cbs.forEach((cb: Function) => {
                        cb(data);
                    });
                    resolve(data);
                });
            } else {
                if (!busData[busKey]) {
                    busData[busKey] = {
                        paramList: [params],
                        url,
                        timer: setTimeout(sendRequest, collectTime)
                    };
                } else {
                    busData[busKey].paramList.push(params); // 加入参数队列
                    if (busData[busKey].paramList.length >= maxComboNum) {
                        // 发起请求
                        sendRequest();
                    }
                }
            }
        }
    }).catch((ex) => {
        if (errorHandle) {
            errorHandle(ex);
            return;
        }
        throw ex;
    });
};

const applyRequest = async (url: string, params: object, requestMethod = 'get', request: any, ) => {
    if (requestMethod === 'get') {
        return request[requestMethod](url, { params });
    } else {
        return request[requestMethod](url, { ...params });
    }

调用

const ApiData = {
    getPrice: {
        url: '//test/prices',
        maxComboNum: 10,
        requestMethod: 'get',
        pack (paramList: object[]) {
            const skuids: string[] = [];
            paramList.forEach((p: any) => {
                if (p.skuids) {
                    skuids.push(p.skuids);
                }
            });
            const ret = {
                skuids: skuids.join(',')
            };

            console.log('合并后的价格参数', ret);
            return ret;
        },
        unpack: (data: any, paramList: object[]) => {
            if (data && data.data && length) {
                const resData = data.data || [];
                const ret = {};
                paramList.forEach((p: any) => {
                    const key = JSON.stringify(p);
                    resData.some((item: any, i: number) => {
                        const sku = item.sku;
                        if (sku === p.skuids) {
                            ret[key] = [data[i]];
                            return true;
                        }
                        return false;
                    });
                });
                console.log('价格拆解数据', ret);
                return ret;
            }
            return [];
        }
    }
};

const p1 = requestCombo(ApiData['getPrice'], { skuids: '11111' }, (data: any) => {
    console.log(data);
});
const p2 = requestCombo(ApiData['getPrice'], { skuids: '11112' }, (data: any) => {
    console.log(data);
});
const p3 = requestCombo(ApiData['getPrice'], { skuids: '11113' }, (data: any) => {
    console.log(data);
});
const data1 = await p1;
const data2 = await p2;
const data3 = await p3;

CodingMeUp avatar Jul 20 '20 11:07 CodingMeUp