0

我是如何控制1000 个 Echarts 实例同时渲染 最大并发的

 3 months ago
source link: https://segmentfault.com/a/1190000041315443
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is damaged, please click the button below to view the snapshot at that time.

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>

小彩蛋✌️, 红盾说服张三买了个高性能的电视机 完美解决...

如有不对,欢迎指正🌟 觉得有帮助,欢迎三连🌟


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK