NodeJS读取Excel文件后无法删除本地文件问题排查
问题分析与解决
我在S3 Bucket托管了文件,通过Axios请求获取Excel文件,随后将其写入本地文件系统。接着使用XLSX库将Excel文件数据转换为JSON并作为响应返回。返回响应后,我想要删除本地的该Excel文件。
尝试删除文件时,删除操作失败,甚至读取文件也失败;但如果本地已存在该文件,则能正常读取并返回响应。
原代码
const response = await axios.get(data.me_url, { responseType: 'arraybuffer' }); if (response) { const ostream = await fs.createWriteStream(`./${filename}`, 'binary'); ostream.write(response.data); ostream.end(); const file = xlsx.readFile(`./${filename}`); const sheetNames = file.SheetNames; const totalSheets = sheetNames.length; let parsedData = []; for (let i = 0; i < totalSheets; i++) { const tempData = xlsx.utils.sheet_to_json(file.Sheets[sheetNames[i]]); tempData.shift(); parsedData.push(...tempData); } if (parsedData.length > 0) { console.log('if filename',filename); try { fs.unlinkSync(`./${filename}`); console.log("Delete File successfully."); return res.status(200).send({ 'data': parsedData, 'message': 'Success', 'code': 200 }); } catch (error) { console.log(error); } } }
错误原因
- 核心问题:文件写入未完成就执行后续操作
fs.createWriteStream是异步操作,调用ostream.end()后文件并不会立刻写入完成。此时直接执行xlsx.readFile会读取到不完整的文件(甚至文件还未创建成功),导致读取失败;后续删除操作也会因为文件处于占用状态或不存在而失败。 - 额外问题:
await fs.createWriteStream无效,因为createWriteStream返回的是流对象而非Promise,await无法等待写入完成。
解决方法
方法1:用Promise封装流的写入完成事件
将流写入过程包装为Promise,确保写入完成后再执行读取/删除:
const response = await axios.get(data.me_url, { responseType: 'arraybuffer' }); if (response) { // 封装流写入为Promise,等待写入完成 await new Promise((resolve, reject) => { const ostream = fs.createWriteStream(`./${filename}`, 'binary'); ostream.write(response.data); ostream.end(); ostream.on('finish', resolve); ostream.on('error', reject); }); const file = xlsx.readFile(`./${filename}`); const sheetNames = file.SheetNames; const totalSheets = sheetNames.length; let parsedData = []; for (let i = 0; i < totalSheets; i++) { const tempData = xlsx.utils.sheet_to_json(file.Sheets[sheetNames[i]]); tempData.shift(); parsedData.push(...tempData); } if (parsedData.length > 0) { console.log('if filename', filename); try { fs.unlinkSync(`./${filename}`); console.log("Delete File successfully."); return res.status(200).send({ 'data': parsedData, 'message': 'Success', 'code': 200 }); } catch (error) { console.log(error); return res.status(500).send({ 'message': '删除临时文件失败', 'code': 500 }); } } }
方法2:使用fs.promises.writeFile简化操作
既然已获取完整的arraybuffer,无需使用流,直接用异步写入方法更简洁:
const response = await axios.get(data.me_url, { responseType: 'arraybuffer' }); if (response) { // 异步写入文件,await确保写入完成 await fs.promises.writeFile(`./${filename}`, response.data, 'binary'); const file = xlsx.readFile(`./${filename}`); const sheetNames = file.SheetNames; const totalSheets = sheetNames.length; let parsedData = []; for (let i = 0; i < totalSheets; i++) { const tempData = xlsx.utils.sheet_to_json(file.Sheets[sheetNames[i]]); tempData.shift(); parsedData.push(...tempData); } if (parsedData.length > 0) { console.log('if filename', filename); try { await fs.promises.unlink(`./${filename}`); // 改用异步删除,避免阻塞事件循环 console.log("Delete File successfully."); return res.status(200).send({ 'data': parsedData, 'message': 'Success', 'code': 200 }); } catch (error) { console.log(error); return res.status(500).send({ 'message': '删除临时文件失败', 'code': 500 }); } } }
额外优化建议
- 尽量避免使用同步方法(如
unlinkSync),HTTP请求处理中同步操作会阻塞事件循环,影响服务性能,优先用异步的fs.promises方法。 - 给临时文件添加唯一标识(如UUID),避免多请求操作同名文件导致冲突。
- 在
xlsx.readFile外层添加try-catch,处理文件读取失败的情况,防止程序崩溃。
内容的提问来源于stack exchange,提问作者Digvijay Singh Thakur
相关产品推荐
相关产品推荐

