You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Electron加载大量FontFace时,用Worker处理Promise是否合理?

核心问题:用Worker处理大量FontFace加载Promise是否合理?

一、先搞懂为什么大量Promise会让主进程卡顿

Promise确实是异步的,但它的then/catch回调都是跑在主事件循环里的。当数万个字体加载的Promise同时完成(不管成功还是失败),海量回调会挤爆主进程的事件队列——主进程既要处理这些回调,还要管UI渲染、用户点击这些核心任务,卡顿自然就来了。另外你代码里的document.fonts.add(ff)是同步操作,大量执行这类操作也会直接阻塞主线程。

二、用Worker处理的合理性完全成立

这么做不仅合理,还非常对症,原因有三个:

  • 给主进程减负:把FontFace创建、加载的逻辑全丢去Worker,主进程只负责接收结果,不用再被海量回调占满事件循环,UI自然流畅。
  • 错误隔离:单个字体加载失败的错误处理在Worker里执行,不会污染主进程的事件队列,避免一个错误拖垮整个渲染流程。
  • 并行潜力大:可以根据CPU核心数创建多个Worker,把任务拆分并行处理,效率能再上一个台阶。

不过你现在的代码有几个硬伤得改:

  1. Worker里没有DOM环境,document.fonts.add(ff)会直接报错,必须把加载成功的字体信息传回主进程,在主进程里执行添加操作。
  2. 循环里逐个postMessage会造成消息队列拥堵,建议批量发任务或者控制并发数。
  3. 主进程没监听Worker的返回消息和错误,等于白忙活。

三、优化后的代码示例

主进程(renderer.js)

myAPI.onFontListReady(jsonFontList => {
  const worker = new Worker('worker.js');
  
  // 接收Worker返回的加载结果
  worker.addEventListener('message', (e) => {
    const [objFontData, errorMsg] = e.data;
    if (errorMsg) {
      console.error(`字体加载失败:${objFontData.newFileName}`, errorMsg);
      return;
    }
    // 主进程中执行字体添加操作
    const ff = new FontFace(`_${objFontData.newFileName}`, `url('${objFontData.newFilePath.replace(/\\/g, '/')}')`);
    document.fonts.add(ff);
  });

  // 监听Worker自身的错误
  worker.addEventListener('error', (err) => {
    console.error('Worker运行出错:', err.message);
  });

  // 批量发送任务,避免消息队列过载
  const batchSize = 50;
  for (let i = 0; i < jsonFontList.length; i += batchSize) {
    const batch = jsonFontList.slice(i, i + batchSize);
    worker.postMessage({
      command: 'batch-load',
      fontBatch: batch
    });
  }
});

Worker进程(worker.js)

addEventListener('message', async (message) => {
  if (message.data.command === 'batch-load') {
    await processFontBatch(message.data.fontBatch);
  }
});

async function processFontBatch(fontBatch) {
  // 控制并发数,避免同时发起太多加载请求耗尽资源
  const concurrencyLimit = 10;
  const results = [];

  // 分块处理,每块控制并发数
  for (let i = 0; i < fontBatch.length; i += concurrencyLimit) {
    const chunk = fontBatch.slice(i, i + concurrencyLimit);
    const chunkPromises = chunk.map(async (objFontData) => {
      try {
        const ff = new FontFace(`_${objFontData.newFileName}`, `url('${objFontData.newFilePath.replace(/\\/g, '/')}')`);
        await ff.load();
        return [objFontData, null];
      } catch (error) {
        console.error(`加载字体出错:${objFontData.newFileName}`, error);
        return [objFontData, error.message];
      }
    });
    const chunkResults = await Promise.all(chunkPromises);
    results.push(...chunkResults);
  }

  // 逐个返回结果(也可以批量返回,进一步减少通信次数)
  results.forEach(result => postMessage(result));
}

四、额外优化技巧

  • 动态调整并发数:可以根据当前系统资源(比如CPU使用率)调整Worker数量和单Worker内的并发加载数。
  • 用Transferable Objects传递大资源:如果是直接读取字体文件的ArrayBuffer,可以用Transferable方式传递,减少内存拷贝开销。
  • 任务队列调度:如果字体数量特别大,Worker可以维护一个任务队列,逐步处理,避免一次性加载压力过大。

内容的提问来源于stack exchange,提问作者oldboy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 03:43:11