我是如何控制1000 個 Echarts 例項同時渲染 最大併發的

mikechen發表於2022-01-21

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 (自行腦補)...

直接把瀏覽器給淦奔潰了...沒錯, 紅盾 此時的內心

image.png

這時候 他想起了 後端大佬 老黃,老黃給了他一個思路 控制併發

實現控制併發

實現併發

  1. 首先我們來實現一個控制併發函式

想直接進入主題的同學可以直接跳到 完整程式碼 檢視

/**
 * @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)
}
  1. 改造 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 函式

我們必須保證一個圖表渲染完成,再執行下一個渲染,此時我們就需要監聽 Echartsfinished 事件

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>

小彩蛋

小彩蛋✌️, 紅盾說服張三買了個高效能的電視機 完美解決...

如有不對,歡迎指正? 覺得有幫助,歡迎三連?

相關文章