Demo 地址
先上 demo 地址
https://codepen.io/firstblood...
專案背景
隨著 面向領導程式設計
越來越深入人心, 看板
專案想必是每個前端開發專家的必修之路
產品經理 張三
要求 前端開發專家 紅盾
做一個由 1000個圖表定時重新整理
的公司最新的財務支出情況
紅盾
在 chrome
上 一頓操作猛如虎,沒兩三天就把 專案搞定了 交付給產品經理 張三
.
無併發控制
讓我們來看看 紅盾
此時大致的程式碼情況
<template>
<div class="app-container">
<div class="charts">
<div v-for="item in domList" :id="item" :key="item" class="chart" />
</div>
</div>
</template>
<script>
const echarts = require("echarts");
const chartNum = 1000; // 圖表數量
const chartIntervalTime = 2000; // 圖表定時渲染毫秒數
export default {
data() {
return {
domList: [],
chartObjs: {},
chartData: [150, 230, 224, 218, 135, 147, 260],
};
},
mounted() {
// 建立echart並繪圖
this.createChart();
// 隔3秒更新圖表資料並渲染
this.intervalChartData(chartIntervalTime);
},
methods: {
// 建立echart並繪圖
async createChart() {
for (let i = 1; i <= chartNum; i++) {
this.domList.push("chart" + i);
}
this.$nextTick(this.renderChartList);
},
async renderChartList() {
this.domList.forEach((dom) => this.initChart(dom));
},
// 隔3秒更新圖表資料並渲染
intervalChartData(s) {
setInterval(() => {
this.renderChartList();
}, s);
},
// 初始化圖表
initChart(domId) {
if (!this.chartObjs[domId]) {
this.chartObjs[domId] = echarts.init(document.getElementById(domId));
}
const option = {
xAxis: {
type: "category",
data: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
},
yAxis: {
type: "value",
},
series: [
{
data: this.chartData,
type: "line",
},
],
};
this.chartObjs[domId].clear();
this.chartObjs[domId].setOption(option);
},
},
};
</script>
<style scoped>
.chart {
float: left;
width: 360px;
height: 300px;
margin: 10px;
border: 2px solid #ff9900;
}
</style>
結果 張三
不按套路出牌, 在電視機上偷偷安了個 瀏覽器(效能極差),然後輸入地址 xxx.com
(自行腦補)...
直接把瀏覽器給淦奔潰了...沒錯, 紅盾
此時的內心
這時候 他想起了 後端大佬 老黃
,老黃給了他一個思路 控制併發
實現控制併發
實現併發
- 首先我們來實現一個控制
併發函式
想直接進入主題的同學可以直接跳到 完整程式碼
檢視
/**
* @params {Number} poolLimit -最大併發限制數
* @params {Array} array -所有的併發請求|渲染陣列
* @params {Function} iteratorFn -對應執行的併發函式(接受 array 的每一項值)
*/
async function asyncPool(poolLimit, array, iteratorFn) {
const ret = [] // 所有執行中的 promises
let executing = [] // 正在執行中的 promises
for (const item of array) {
//接受 iteratorFn 的返回值:Promise
const p = Promise.resolve().then(() => iteratorFn(item))
ret.push(p)
// 如果執行的陣列 大於等於 最大併發限制 那麼我們就要控制併發
if (array.length >= poolLimit) {
const e = p.then(() => executing.splice(executing.indexOf(e), 1))
// p.then 返回的 一個Promise 我們把它放到正在執行陣列中,一旦執行完 便剔除對應的值
executing.push(e)
//核心程式碼:正在執行的 promises 陣列 大於等於 `最大併發限制` 用.race 方法釋放一個執行最快的
if (executing.length >= poolLimit) await Promise.race(executing)
}
}
//返回一個 Promise.all
return Promise.all(ret)
}
改造
renderChartList
函式(核心
)async renderChartList() { // 這裡的 MAX_CURRENT 之後可以自定義一個數字 await asyncPool(MAX_CURRENT, this.domList, (dom) => { // 我們在這裡必須返回一個 promise 對應為 `併發函式` 的 `p` 變數 return new Promise(async (resolve) => { const res = await this.initChart(dom); resolve(res);// 這一步之後, 對應執行 `併發函式` 的 p.then 剔除 }).then((data) => { console.log(data); return data; }); }); }
3.改造 initChart
函式
我們必須保證一個圖表渲染完成,再執行下一個渲染,此時我們就需要監聽 Echarts
的 finished
事件
initChart(domId) {
// 我們 把它改造成一個 promise 函式
return new Promise((resolve) => {
...
// 核心程式碼 監聽 echarts 的 finished
this.chartObjs[domId].on("finished", () => {
resolve(domId);// 對應 上一步的 `const res = await this.initChart(dom);`
});
});
}
4.改造 intervalChartData
函式
我們必須保證併發執行完 所有的圖表渲染,再進入下一個定時器邏輯
判斷 executing
的長度即可(此時應該把 executing
獨立為全域性變數)
intervalChartData(s) {
setInterval(() => {
if (executing.length > 0) return; // 還有正在執行的渲染 不重複新增
this.renderChartList();
}, s);
}
完整程式碼
附上完整程式碼
<template>
<div class="app-container">
<div class="charts">
<div v-for="item in domList" :id="item" :key="item" class="chart" />
</div>
</div>
</template>
<script>
const echarts = require("echarts");
const chartNum = 1000; // 圖表數量
const MAX_CURRENT = 50; // 圖表最大渲染併發數
const chartIntervalTime = 2000; // 圖表定時渲染毫秒數
let executing = [];
/**
* @params {Number} poolLimit -最大併發限制數
* @params {Array} array -所有的併發請求|渲染陣列
* @params {Function} iteratorFn -對應執行的併發函式(接受 array 的每一項值)
*/
async function asyncPool(poolLimit, array, iteratorFn) {
const ret = []; // 所有執行中的 promises
executing = []; // 正在執行中的 promises
for (const item of array) {
const p = Promise.resolve().then(() => iteratorFn(item));
ret.push(p);
if (array.length >= poolLimit) {
const e = p.then(() => executing.splice(executing.indexOf(e), 1));
executing.push(e);
if (executing.length >= poolLimit) await Promise.race(executing);
}
}
return Promise.all(ret);
}
export default {
data() {
return {
domList: [],
chartObjs: {},
chartData: [150, 230, 224, 218, 135, 147, 260],
};
},
mounted() {
// 建立echart並繪圖
this.createChart();
// 隔3秒更新圖表資料並渲染
this.intervalChartData(chartIntervalTime);
},
methods: {
// 建立echart並繪圖
async createChart() {
for (let i = 1; i <= chartNum; i++) {
this.domList.push("chart" + i);
}
this.$nextTick(this.renderChartList);
},
async renderChartList() {
const res = await asyncPool(MAX_CURRENT, this.domList, (i, arr) => {
return new Promise(async (resolve) => {
const res = await this.initChart(i);
resolve(res);
}).then((data) => {
console.log(data);
return data;
});
});
},
// 隔3秒更新圖表資料並渲染
intervalChartData(s) {
setInterval(() => {
if (executing.length > 0) return; // 還有正在執行的渲染 不重複新增
this.renderChartList();
}, s);
},
// 初始化圖表
initChart(domId) {
return new Promise((resolve) => {
if (!this.chartObjs[domId]) {
this.chartObjs[domId] = echarts.init(document.getElementById(domId));
}
const option = {
xAxis: {
type: "category",
data: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
},
yAxis: {
type: "value",
},
series: [
{
data: this.chartData,
type: "line",
},
],
};
this.chartObjs[domId].clear();
this.chartObjs[domId].setOption(option);
this.chartObjs[domId].on("finished", () => {
resolve(domId);
});
});
},
},
};
</script>
<style scoped>
.chart {
float: left;
width: 360px;
height: 300px;
margin: 10px;
border: 2px solid #ff9900;
}
</style>
小彩蛋
小彩蛋✌️, 紅盾
說服張三
買了個高效能的電視機 完美解決...
如有不對,歡迎指正? 覺得有幫助,歡迎三連?