使用res.sendFile()时如何等待文件创建完成后再向客户端返回响应
文件生成后再返回客户端的解决方案
问题根因
carbone.render是异步回调API,回调内部的文件写入逻辑会被放入异步任务队列等待执行,而你放在回调外部的res.sendFile会同步优先执行,因此出现文件还没写入完成就尝试返回的问题。另外回调中的process.exit()会直接终止Node进程,正常服务场景下禁止使用该语句。
方案1:回调嵌套实现(最简改动)
直接把res.sendFile逻辑移动到carbone.render的回调内部,等文件写入完成后再执行返回逻辑,同时补充错误响应处理:
carbone.render("./template.odt", data, options, (err, ress) => { if (err) { console.log(err); return res.status(500).send('报告生成失败'); } const timestamp = Date.now(); // 建议在回调内部生成timestamp,避免多请求冲突 const paths = `./static/reports/report-${timestamp}.pdf`; try { fs.writeFileSync(paths, ress); // 写入完成后再执行返回 res.sendFile( path.join(__dirname, "..", "static", "reports", `report-${timestamp}.pdf`) ); } catch (writeErr) { console.log(writeErr); return res.status(500).send('文件写入失败'); } }); // 删掉原来写在外部的res.sendFile逻辑
方案2:async/await实现(更易维护)
使用Node内置的util.promisify将回调式API转为Promise,配合async/await语法编写,逻辑更清晰,避免回调嵌套:
const { promisify } = require('util'); const carboneRender = promisify(carbone.render); const fsWriteFile = promisify(fs.writeFile); // 你的路由处理函数需要加async修饰 async function handleReportReq (req, res) { try { const ress = await carboneRender("./template.odt", data, options); const timestamp = Date.now(); const filePath = path.join(__dirname, "..", "static", "reports", `report-${timestamp}.pdf`); await fsWriteFile(filePath, ress); res.sendFile(filePath); } catch (err) { console.log(err); res.status(500).send('报告生成失败'); } }
注意事项
- 建议在生成文件的逻辑内部生成timestamp,避免多个请求共用全局timestamp导致文件覆盖、读取错误
- 生产环境建议定期清理生成的临时报告文件,避免磁盘空间被占满
- 所有错误场景都要给客户端返回对应状态码和提示,不要仅在服务端打印日志
内容的提问来源于stack exchange,提问作者Arya Lisa
相关产品推荐
相关产品推荐

