前端可以不用依赖后端实现导出大数据了


theme: channing-cyan
hightlight: channing-cyan

前言

在我们公司表格数据导出都是前端去处理。一开始数据量不大,倒没什么问题。但随着数据量的加大,问题也逐渐暴露出来。

一天的数据量有一来万条,导出一定时间范围的数据,30天就得30来万条数据。

那会测试直接给我导出 60 万条数据都存到一个 Excel 表中,页面直接卡死掉,动都动不了,后面直接崩溃掉。

那会为什么导出选择由前端去做呢?

  • 多语言问题:有些内置数据(如:文件分类,计算机组等信息)需要支持多语言,以及表格 header 头。
  • 数据转换问题:有些内置数据返回的是数值类型,需要转成对应的真正的数据。
  • 导出表格字段问题:用户可以通过切换列来控制具体导出哪些字段。

排除原因

经过排查:导出大量数据通常涉及大量的计算、DOM 操作或文件生成等复杂操作,这些操作会在主线程中执行。如果这些操作耗时过长,主线程会被阻塞,导致页面无法响应用户交互(如点击、滚动等),表现为页面卡死

那是否把这些大量的计算、DOM 操作或文件生成等复杂操作,放到子进进程去处理,不就解决了吗?

这就说到了今天的主角:Web Workers

Web Workers 介绍

Web Workers 使得一个Web应用程序可以在与主线程分离的后台线程中运行一个脚本。

这样做的好处在于可以在一个单独的线程中执行费时的处理任务,从而允许主(通常是UI)线程运行而不被阻塞。

它的作用就是给JS创造多线程运行环境,允许主线程创建worker线程,分配任务给后者,主线程运行的同时worker线程也在运行,相互不干扰,在worker线程运行结束后把结果返回给主线程。这样做的好处是主线程可以把计算密集型或高延迟的任务交给worker线程执行,这样主线程就会变得轻松,不会被阻塞或拖慢。这并不意味着JS语言本身支持了多线程能力,而是浏览器作为宿主环境提供了JS一个多线程运行的环境。

不过因为worker一旦新建,就会一直运行,不会被主线程的活动打断,这样有利于随时响应主线程的通性,但是也会造成资源的浪费,所以不应过度使用,用完注意关闭。或者说:如果worker无实例引用,该worker空闲后立即会被关闭;如果worker实列引用不为0,该worker空闲也不会被关闭。

Web Workers 使用

  1. 创建 Worker 对象:通过 new Worker(url) 创建一个 Worker 对象,这里的 url 指向你预先编写的 JavaScript 文件路径,这个文件内包含 Workers 将要执行的脚本内容。
  2. 发送消息:你可以使用 worker.postMessage(message) 方法从主脚本向 Worker 发送数据。
  3. 处理 Worker 发送的消息:在主脚本中,设置 worker.onmessage 事件监听器来处理 Worker 发回来的数据。
  4. 终止 Worker:如果不再需要 Worker,可以调用 worker.terminate() 方法来停止 Worker。
  5. 监听错误:可以通过添加 onerror 事件监听器来处理 Worker 中可能出现的错误。

主线程脚本

  const myWorker = new Worker('worker.js')
  const nums = [10, 20]

  myWorker.postMessage(nums)

  myWorker.onmessage = function(e) {
    result = e.data
    console.log('主进程接收子进程传递回来的数据:', e.data)
    // 停止 Worker
    worker.terminate()
  }

  myWorker.onerror = function(e) {
    console.log('监听错误')
  }

Worker 脚本

onmessage = function(e) {
  var data = e.data;
  var result = data[0] * data[1];
  postMessage(result);
}

Web Workers 实战 Excel 导出

基本案例有了,但还是遇到一些坑。下面开始一个个填坑。

问题1:vue 项目如何配置 web worker

这里需要下载第三方 loader, 来编译 workers 脚本。

npm install worker-loader@3.0.8

接下来,修改 vue.config.js 文件:

// vue.config.js
module.exports = {
  chainWebpack(config) {
    config.module
      .rule('worker')
      .test(/\.worker\.js$/)
      .use('worker-loader')
      .loader('worker-loader')
      .options({})
      .end()  
  }
}

注意test() 设置了文件名后缀是 .worker.js 则为 worker 脚本文件

到这里第一个问题就解决了。。。


问题2:修改了 web worker 后,重新编译打包没有生效

vue项目一改动到代码文件就会重新编译。

但在调试过程中,修改了 worker 脚本,发现一直没有修复到问题,一开始也是很怀疑自己是不是逻辑出错了。

通过 debug 才发现,代码一直没有修改。

后面每次修改 worker 脚本,都会重新启动 vue 项目,一开始问题是解决了。

但偶尔还是会没有修改到代码。

最终排查到:原来是每次重新编译时,要删除掉 node_modules 目录下的 .cache 文件夹
在这里插入图片描述

才会重新加载新 worker 脚本代码

问题3:主进程向子进程发送参数时,若参数存在对象,会报错

这里主要是生产 csvData 数据(key: value)中的 value 是一个对象结构时,发送给到 子进程,浏览器会报错。

这里解决方法是:将 value 进行序列化处理

// * 判断 csvData 中的值是否存在对象,需要序列化处理
const keys = csvHeader.map(item => item.key)
csvData = csvData.map(row => {
  return keys.reduce((acc, prev) => {
    acc[prev] = typeof row[prev] === 'object' ? JSON.stringify(row[prev]) : row[prev]
    return acc
  }, {})
})

问题4:在子进程中下载文件失败

主进程去结合实际业务逻辑生成 csvHeadercsvData 数据后,发送给到子进程,由其生成 Excel 文件流,并下载下来。

// 主进程
const { csvHeader, csvData } = generateExcelData(data)


// 子进程
import Excel from 'exceljs'
self.onmessage = async function(e) {
  const { csvData, csvHeader } = e.data

  const workbook = new Excel.Workbook()
  const worksheet = workbook.addWorksheet('My Sheet')

  worksheet.columns = csvHeader
  csvData.forEach(row => worksheet.addRow(row))

  // 生成 Excel 文件的 Buffer
  const excelBuffer = await workbook.xlsx.writeBuffer()

  // TODO 下载文件
}

经过调试发现文件下载不下来,查阅资料得出:

主要原因在于 Web Workers 的设计限制。具体来说,Web Workers 没有直接访问浏览器的 DOM 和一些与用户界面交互的功能,包括文件下载

所以这里只能将 Excel 文件的 Buffer转成blog发送给到主进程进行文件下载。
主进程

import { saveAs } from 'file-saver'
import ExportWorker from './export.worker.js'
const worker = new ExportWorker()
worker.postMessage({
  csvData: csvData,
  csvHeader: csvHeader
})

worker.onmessage = async(e) => {
  const { chunk: blog } = e.data
  saveAs(blog, filename)
}

worker 脚本

import Excel from 'exceljs'

self.onmessage = async function(e) {
  const { csvData, csvHeader } = e.data

  const workbook = new Excel.Workbook()
  const worksheet = workbook.addWorksheet('My Sheet')

  worksheet.columns = csvHeader
  csvData.forEach(row => worksheet.addRow(row))

  // 生成 Excel 文件的 Buffer
  const excelBuffer = await workbook.xlsx.writeBuffer()

  const blob = new Blob([excelBuffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })

  self.postMessage({ chunk: blob })
}

源码

主进程

import { saveAs } from 'file-saver'
import ExportWorker from './export.worker.js'
/**
* 导出数据为 XLSX(通过 web Worker)
* @param {Object} csvHeader XLSX 头
* @param {Array} csvData 数据
* @param {String} filename 文件名
*/
const exportDataToXLSXByWorker = (csvHeader, csvData, filename) => {
    const worker = new ExportWorker()

    // * 判断 csvData 中的值是否存在对象,需要序列化处理
    const keys = csvHeader.map(item => item.key)
    csvData = csvData.map(row => {
      return keys.reduce((acc, prev) => {
        acc[prev] = typeof row[prev] === 'object' ? JSON.stringify(row[prev]) : row[prev]
        return acc
      }, {})
    })

    worker.postMessage({
      csvData: csvData,
      csvHeader: csvHeader
    })

    worker.onmessage = async(e) => {
      const { chunk: blog } = e.data
      saveAs(blog, filename)
    }
}

worker 脚本

import Excel from 'exceljs'

self.onmessage = async function(e) {
  const { csvData, csvHeader } = e.data

  const workbook = new Excel.Workbook()
  const worksheet = workbook.addWorksheet('My Sheet')

  worksheet.columns = csvHeader
  csvData.forEach(row => worksheet.addRow(row))

  // 生成 Excel 文件的 Buffer
  const excelBuffer = await workbook.xlsx.writeBuffer()

  const blob = new Blob([excelBuffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })

  self.postMessage({ chunk: blob })
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/968593.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

本地部署DeepSeek Nodejs版

目录 1.下载 Ollama 2.下载DeepSeek模型 3.下载 ollama.js 1.下载 Ollama https://ollama.com/ 下载之后点击安装,等待安装成功后,打开cmd窗口,输入以下指令: ollama -v 如果显示了版本号,则代表已经下载成功了。…

C++ Primer 迭代语句

欢迎阅读我的 【CPrimer】专栏 专栏简介:本专栏主要面向C初学者,解释C的一些基本概念和基础语言特性,涉及C标准库的用法,面向对象特性,泛型特性高级用法。通过使用标准库中定义的抽象设施,使你更加适应高级…

你需要提供管理员权限才能删除此文件夹解决方法

立即高级启动 windows10 搜索“设置”,然后“更新和安全””->“恢复”->“立即重新启动” windows11 搜索“设置”,然后“Windows更新”->“更新历史记录”->“恢复”->“立即重新启动” 疑难解答 点击“疑难解答” 高级选项 启…

408-数据结构

数据结构在学什么? 1.用代码把问题信息化 2.用计算机处理信息 ch1 数据:数据是信息的载体,是描述客观事物属性的数、字符及所有能输入到计算机中并被计算机程序识别和处理的符号的集合。数据是计算机程序加工的原料。 ch2 //假设线性表…

神经网络常见激活函数 9-CELU函数

文章目录 CELU函数导函数函数和导函数图像优缺点pytorch中的CELU函数tensorflow 中的CELU函数 CELU 连续可微指数线性单元:CELU(Continuously Differentiable Exponential Linear Unit),是一种连续可导的激活函数,结合了 ELU 和 …

Ceph集群搭建2025(squid版)

squid版本维护年限 apt install -y cephadmecho >> "deb http://mirrors.163.com/ceph/debian-squid/ bookworm main" echo >> "deb-src http://mirrors.163.com/ceph/debian-squid/ bookworm main"#安装源 cephadm install #开始初始化一个最…

详解电子邮箱工作原理|SMTP、POP3、IMAP、SPF、MIME

写在前面 电子邮件(Email)是一种通过互联网进行异步通信的技术,工作原理涉及多个协议、服务器和客户端协同工作。 接下来我们来介绍一下电子邮箱的工作原理 1. 电子邮件的核心组成部分 邮件客户端:用户直接交互的软件&#xf…

【安全靶场】信息收集靶场

靶场:https://app.hackinghub.io/hubs/prison-hack 信息收集 子域名收集 1.subfinder files.jabprisons.com staging.jabprisons.com cobrowse.jabprisons.com a1.top.jabprisons.com cf1.jabprisons.com va.cobrowse.jabprisons.com vs.jabprisons.com c…

LVDS接口总结--(5)IDELAY3仿真

仿真参考资料如下: https://zhuanlan.zhihu.com/p/386057087 timescale 1 ns/1 ps module tb_idelay3_ctrl();parameter REF_CLK 2.5 ; // 400MHzparameter DIN_CLK 3.3 ; // 300MHzreg ref_clk ;reg …

DeepSeek的大模型介绍

文章目录 DeepSeek是什么DeepSeek平台使用DeepSeek的使用场景DeepSeek的本地部署 DeepSeek是什么 DeepSeek是一家2023/7月年成立的人工智能公司,致力于开发高效、高性能的生成式AI模型,在短短一年多的时间里推出了多款强大的开源模型,包括De…

【devops】Github Actions Secrets | 如何在Github中设置CI的Secret供CI的yaml使用

一、Github Actions 1、ci.yml name: CIon: [ push ]jobs:build:runs-on: ubuntu-lateststeps:- name: Checkout codeuses: actions/checkoutv3- name: Set up Gouses: actions/setup-gov4with:go-version: 1.23.0- name: Cache Go modulesuses: actions/cachev3with:path: |…

C语言基本概念————讨论sqrt()和pow()函数与整数的关系

本文来源:C语言基本概念——讨论sqrt()和pow()函数与整数的关系. C语言基本概念——sqrt和pow函数与整数的关系 1. 使用sqrt()是否可以得到完全平方数的精确的整数平方根1.1 完全平方数的计算结果是否精确?1.2 为什么不会出现误差(如 1.99999…

日常知识点之面试后反思裸写string类

1:实现一个字符串类。 简单汇总 最简单的方案,使用一个字符串指针,以及实际字符串长度即可。 参考stl的实现,为了提升string的性能,实际上单纯的字符串指针和实际长度是不够了,如上,有优化方案…

【AI论文】10亿参数大语言模型能超越405亿参数大语言模型吗?重新思考测试时计算最优缩放

摘要:测试时缩放(Test-Time Scaling,TTS)是一种通过在推理阶段使用额外计算来提高大语言模型(LLMs)性能的重要方法。然而,目前的研究并未系统地分析策略模型、过程奖励模型(Process …

【漫话机器学习系列】088.常见的输出层激活函数(Common Output Layer Activation Functions)

在神经网络中,输出层(Output Layer) 的激活函数(Activation Function)直接决定了模型的输出形式,并影响损失函数的选择及训练效果。不同的任务类型(如分类或回归)需要使用不同的激活…

在实体机和wsl2中安装docker、使用GPU

正常使用docker和gpu,直接命令行安装dcoker和,nvidia-container-toolkit。区别在于,后者在于安装驱动已经cuda加速时存在系统上的差异。 1、安装gpu驱动 在实体机中,安装cuda加速包,我们直接安装 driver 和 cuda 即可…

麒麟v10 server版安装ollama跑Deepseek

麒麟v10 server版安装ollama跑Deepseek 1. 环境 2. 安装docker yum install docker 发现源只有18.x版本,启动ollama,发现调用CPU,没调用GPU docker19.x以上才 会调用GPU, 可以添加centos8的原,安装docker-ce3.启动ollama&#…

LabVIEW用户界面(UI)和用户体验(UX)设计

作为一名 LabVIEW 开发者,满足功能需求、保障使用便捷与灵活只是基础要求。在如今这个用户体验至上的时代,为 LabVIEW 应用程序设计直观且具有美学感的界面,同样是不容忽视的关键任务。一个优秀的界面设计,不仅能提升用户对程序的…

如何使用Java语言在Idea和Android中分别建立服务端和客户端实现局域网聊天

手把手教你用Java语言在Idea和Android中分别建立服务端和客户端实现局域网聊天 目录 文章目录 手把手教你用**Java**语言在**Idea**和**Android**中分别建立**服务端**和**客户端**实现局域网聊天**目录**[toc]**基本实现****问题分析****服务端**Idea:结构预览Server类代码解…

【实战篇】DeepSeek + ElevenLabs:让人工智能“开口说话”,打造你的专属语音助手!

最近,AI语音合成技术真是火得不行,各种“开口脆”的AI声音层出不穷,听得我直呼“这也太像真人了吧!” 作为一个科技爱好者,我当然不能错过这股潮流,这不,最近就沉迷于用 DeepSeek 和 ElevenLabs 这两款神器,捣鼓各种人声音频,简直停不下来! 先来科普一下这两位“主角…