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

Node.js循环处理大量Base64数据调用writeFile生成0字节文件问题

问题原因
  • 变量作用域问题:循环中使用var声明所有变量,var属于函数级作用域,不存在块级作用域。7000次循环会在瞬间执行完成,此时所有异步writeFile的回调触发时,拿到的NewGambar、base64Data等变量都是循环最后一次的值,会出现大量文件路径错误、写入内容不匹配的问题,小数据量下循环执行慢于回调触发速度才没有暴露问题。
  • 文件描述符耗尽:fs.writeFile是异步API,一次性调用7000次相当于同时向操作系统申请7000个文件写入句柄,操作系统对单个进程的最大打开文件数有限制(通常默认是1024左右),超过限制的写入请求会直接报错,文件写入中断就会生成0字节的空文件。
  • 异步操作无等待逻辑:所有写入操作都是异步触发,主线程不会等待所有写入完成就可能提前退出,部分未完成的写入操作就会生成空文件。
解决方案

优化思路:将模块引入提到循环外避免重复加载,使用fs.promises配合async/await管理异步流程,控制并发数避免耗尽文件描述符,用let/const声明变量利用块级作用域隔离每次循环的参数。
优化后代码如下:

// 模块引入统一放在顶部,避免循环内重复加载
const fs = require('fs/promises')
const isBase64 = require('is-base64')
const path = require('path')

async function saveAllImages(recordset) {
  const extention = ".jpg"
  const dir = "image/customer/"
  // 自动创建目标目录,多层目录不存在也会递归创建,避免目录不存在导致写入失败
  await fs.mkdir(path.join(__dirname, '../', dir), { recursive: true })

  // 并发数可根据设备配置调整,建议不超过50,避免超出系统文件句柄限制
  const concurrency = 10
  for (let i = 0; i < recordset.length; i += concurrency) {
    // 分批处理,每次最多同时处理concurrency个文件
    const currentBatch = recordset.slice(i, i + concurrency)
    // 等待当前批次所有文件写入完成再处理下一批
    await Promise.all(currentBatch.map(async (item) => {
      if (item.Blob == null) return
      const NewGambar = `${item.CustomerID}_${item.CustomerCode}`
      const base64Data = Buffer.from(item.Blob).toString('base64')
      if (!isBase64(base64Data, { allowEmpty: false })) return

      const filePath = path.join(__dirname, '../', dir, `${NewGambar}${extention}`)
      await fs.writeFile(filePath, base64Data, 'base64')
      console.log(`保存成功:${filePath}`)
    }))
  }
  console.log('全部图片处理完成')
}

// 传入数据调用方法即可
saveAllImages(result.recordset).catch(err => console.error('全局处理错误:', err))

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 11:45:01