我如何控制大屏看板 1000个 Echarts 渲染并发?

7,999 阅读1分钟

先按配图 来套肩部spa放松操~

前言

阅读完本文,大概需要 7 分钟 时间

你将会学到

1. 控制 并发渲染

2. 控制 并发 http 请求

3. 把 Promise 玩到花里胡哨

Demo 地址

二话不说,先上 demo 地址

codepen.io/firstblood9…

项目背景

随着 面向领导编程 越来越深入人心, 看板 项目想必是每个前端开发专家的必修之路

产品经理 张三 要求 前端开发专家 红盾 做一个由 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>

小彩蛋

小彩蛋✌️, 红盾说服张三买了个高性能的电视机, 完美解决, 真是专治各种不服...

最后

推荐阅读轮询技能 我如何实现一个轮询函数的封装?

如有不对,欢迎指正🌟 如有帮助,建议小心心大拇指三连🌟