AboutFE
AboutFE copied to clipboard
3、JS动态串行、并行加载脚本, 请求合并
我们可以动态的创建
<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>
前端简易版接口聚合模块 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;