关于前端:前端开发中如何高效渲染大数据量

5次阅读

共计 3708 个字符,预计需要花费 10 分钟才能阅读完成。

咱们是袋鼠云数栈 UED 团队,致力于打造优良的一站式数据中台产品。咱们始终保持工匠精力,摸索前端路线,为社区积攒并流传教训价值。

本文作者:琉易 liuxianyu.cn

  在日常工作中,较少的能遇到一次性往页面中插入大量数据的场景,数栈的离线开发(以下简称离线)产品中,就有相似的场景。本文将分享一个理论场景中的前端开发思路,实现高效的数据渲染,晋升页面性能和用户体验。

一、场景介绍

  在离线的数据开发模块,用户能够在 sql 编辑器中编写 sql,再通过 整段运行 / 分段运行 来执行 sql。在点击 整段运行 后,运行胜利日志打印后到展现后果的过程中,有一段时间页面很卡顿,次要体现为编辑器编写卡顿。

二、性能问题

  咱们是在解决 sql 最大运行行数 问题时,发现了上述须要进行性能优化的场景。
先来梳理下以后代码的设计逻辑:

  • 前端将选中的 sql 传递给服务端,服务端返回一个调度运行的 jobId;
  • 前端接着以该 jobId 轮询服务端,查问工作的执行状态;
  • 当轮询到工作已实现时,选中的 sql 中如果有查问语句,服务端则会按 select 语句的程序返回一个 sqlId 的数组汇合;
  • 前端基于 n 个 sqlId 的汇合,并发 n 个 selectData 的申请;
  • 所有的 selectData 申请实现后渲染数据;

    为了保障后果最终的展现程序和 select 语句程序统一,咱们为单纯的 sqlIdList 循环办法加上了 Promise.allsettled 的办法,使得 n 个 selectData 的申请程序和 select 语句程序统一。

  由上述逻辑能够看出,问题可能呈现在如果选中的 sql 中有大量 select 语句的话,会在「整段运行」实现后大批量申请 selectData 接口,再期待所有 selectData 申请实现后,集中进行渲染。此时,就会呈现一次性往页面中插入大量数据的场景。那么,咱们怎么解决上述问题呢?

三、解决思路

  能够看出,上述逻辑次要有两个问题:

  • 1、大批量申请 selectData 接口;
  • 2、集中性数据渲染。

1、工作分组

  仍旧通过 Promise.allsettled 拿到所有 selectData 接口返回的后果,将原先集中渲染看作是一个大工作,咱们将工作拆分成单个的 selectData 后果渲染工作;再依据理论状况,对单个工作进行分组,比方两个一组,渲染完一组再渲染下一组。
拆分完工作,就波及到了工作的优先级问题,优先级决定了哪个工作先执行。这里采纳最原始的“抢占式轮转”,按 sqlIdList 的程序保留编辑器中的 sql 程序。

Promise.allSettled(promiseList).then((results = []) => {
    const renderOnce = 2; // 每组渲染的后果 tab 数量
    const loop = (idx) => {if (promiseList.length <= idx) return;
        results.slice(idx, idx + renderOnce).forEach((item, idx) => {if (item.status === 'fulfilled') {handleResultData(item?.value || {}, sqlIdList[idx]?.sqlId);
            } else {
                console.error(
                    'selectExecResultDataList Promise.allSettled rejected',
                    item.reason
                );
            }
        });
        setTimeout(() => {loop(idx + renderOnce);
        }, 100);
    };
    loop(0);
});

2、申请分组 + 工作分组

  问题 1 中的大批量申请 selectData 接口,也是一个突破点。咱们能够将申请进行分组,每次以固定数量的 sqlId 去申请 selectData 接口,比方每组申请 6 个 sqlId 的后果,以后组的申请全副完结后再进行渲染;为了保障成果最优,这里也引入工作分组的思路。

const requestOnce = 6; // 每组申请的数量
// 将一维数组转换成二维数组
const sqlIdList2D = convertTo2DArray(sqlIdList, requestOnce);
const idx2D = 0; // sqlIdList2D 的索引

const requestLoop = (index) => {if (!sqlIdList2D[index]) return;
    const promiseList = sqlIdList2D[index].map((item) =>
        selectExecResultData(item?.sqlId)
                                              );
    Promise.allSettled(promiseList)
        .then((results = []) => {
            const renderOnce = 2; // 每组渲染的后果 tab 数量

            const loop = (idx) => {if (promiseList.length <= idx) return;
                results.slice(idx, idx + renderOnce).forEach((item, idx) => {if (item.status === 'fulfilled') {handleResultData(item?.value || {}, sqlIdList[idx]?.sqlId);
                    } else {
                        console.error(
                            'selectExecResultDataList Promise.allSettled rejected',
                            item.reason
                        );
                    }
                });
                setTimeout(() => {loop(idx + renderOnce);
                }, 100);
            };
            loop(0);
        })
        .finally(() => {requestLoop(index + 1);
        });
};
requestLoop(idx2D);

3、申请分组

  上一种计划的代码写进去太难以了解了,属于上午写,下午忘的逻辑,正文也不好写,不利于保护。基于理论状况,咱们尝试下仅对申请作分组解决,看看成果。

const requestOnce = 3; // 每组申请的数量
// 将一维数组转换成二维数组
const sqlIdList2D = convertTo2DArray(sqlIdList, requestOnce);
const idx2D = 0; // sqlIdList2D 的索引

const requestLoop = (index) => {if (!sqlIdList2D[index]) return;
    const promiseList = sqlIdList2D[index].map((item) =>
        selectExecResultData(item?.sqlId)
                                              );
    Promise.allSettled(promiseList)
        .then((results = []) => {results.forEach((item, idx) => {if (item.status === 'fulfilled') {handleResultData(item?.value || {}, sqlIdList[idx]?.sqlId);
                } else {
                    console.error(
                        'selectExecResultDataList Promise.allSettled rejected',
                        item.reason
                    );
                }
            });
        })
        .finally(() => {requestLoop(index + 1);
        });
};
requestLoop(idx2D);

四、思路了解

1、解决大数据量渲染的问题,常见办法有:工夫分片、虚构列表等;
2、解决同步阻塞的问题,常见办法有:工作合成、异步等;
3、如果某个工作执行工夫较长的话,从优化的角度,咱们通常会思考将该工作分解成一系列的子工作。

  在工作分组一节,咱们将 setTimeout 的工夫距离设置为 100ms,也就是我认为最快在 100ms 内能实现渲染;但假如不到 100ms 就实现了渲染,那么就须要白白期待一段时间,这是没有必要的。这时能够思考[window.requestAnimationFrame]() 办法。

- setTimeout(() => {+ window.requestAnimationFrame(() => {loop(idx + renderOnce);
- }, 100);
+ });

  第三节的申请分组,实际上达到了渲染工作分组的成果。本文更多的是提供一个解决思路,上述形式也是基于对工夫分片的了解实际。

五、写在最初

  在软件开发中,性能优化是一个重要的方面,但并不是惟一谋求,往往还须要思考多个因素,包含性能需要、可维护性、安全性等等。依据具体情况,综合应用多种技术和策略,以找到最佳的解决方案。


最初

欢送关注【袋鼠云数栈 UED 团队】~
袋鼠云数栈 UED 团队继续为宽广开发者分享技术成绩,相继参加开源了欢送 star

  • 大数据分布式任务调度零碎——Taier
  • 轻量级的 Web IDE UI 框架——Molecule
  • 针对大数据畛域的 SQL Parser 我的项目——dt-sql-parser
  • 袋鼠云数栈前端团队代码评审工程实际文档——code-review-practices
  • 一个速度更快、配置更灵便、应用更简略的模块打包器——ko
正文完
 0