Express的response.download接口对相同请求返回非标准数据问题求助
问题根因
你遇到的偶发异常是典型的异步竞态问题:文件流写入是异步操作,你当前的createReportFile函数在创建完写入流、还没等所有内容写入完成、文件关闭的情况下就直接返回了文件ID,此时调用response.download读取文件时,文件可能处于以下几种状态:
- 还没创建完成:返回
File not found - 只写完了表头:仅返回表头内容
- 所有内容写入完成:正常返回完整文件
刚好对应你遇到的所有异常表现。
修复方案
第一步:改造createReportFile为异步函数,等待文件写入完成
把原来的同步返回改成返回Promise,监听流的写入完成事件和错误事件,同时记得在所有内容写完后主动结束流:
export const createReportFile = ({ reportLines, fromDate, toDate }: CreateReportProps): Promise<string> => { return new Promise((resolve, reject) => { const startDate = format(fromDate, 'dd-MM-yyyy') const endDate = format(toDate, 'dd-MM-yyyy') const aksReportDate = Date.now() const reportFileId = `${startDate}-to-${endDate}Report${aksReportDate}` const filePath = path.join(__dirname, '..', '..', '..', 'tmp', 'uploads', `${reportFileId}.csv`) const reportFileStream = fs.createWriteStream(filePath) const fieldsLine = 'usuário,primeiro_nome,ultimo_nome,ativo,completou_o_treinamento' reportFileStream.once('open', () => { reportFileStream.write(fieldsLine + '\r\n') reportLines.forEach(({ user_id, first_name, last_name, active, completed }) => { reportFileStream.write(`${user_id},${first_name},${last_name},${active},${completed}\r\n`) }) // 所有内容写入完成后主动结束流 reportFileStream.end() }) // 流写入完成、文件关闭后返回文件ID reportFileStream.on('finish', () => { resolve(reportFileId) }) // 写入出错时抛出错误 reportFileStream.on('error', (err) => { reject(err) }) }) }
第二步:修改下载逻辑,等待文件生成完成后再触发下载
因为现在createReportFile返回的是Promise,需要加await等待执行完成:
// ... const usersReport = // ....generate users report // 加await等待文件生成完成 const reportFileId = await createReportFile({ fromDate, toDate, reportLines: usersReport }) const filePath = path.join(__dirname, '..', '..', '..', 'tmp', 'uploads', `${reportFileId}.csv`) console.log(filePath) return response.download(filePath) // ...
可选优化方案
如果不需要持久化存储报表文件,可以直接在内存生成CSV内容返回,省去本地文件读写、后续清理临时文件的步骤:
const fieldsLine = 'usuário,primeiro_nome,ultimo_nome,ativo,completou_o_treinamento' const csvContent = [ fieldsLine, ...usersReport.map(({ user_id, first_name, last_name, active, completed }) => `${user_id},${first_name},${last_name},${active},${completed}` ) ].join('\r\n') // 直接设置响应头返回CSV response.setHeader('Content-Type', 'text/csv') response.setHeader('Content-Disposition', `attachment; filename="report-${Date.now()}.csv"`) return response.send(csvContent)
内容的提问来源于stack exchange,提问作者Guilhermo Masid
相关产品推荐
相关产品推荐

