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

NestJS控制器操作完成后文件删除异常问题排查与修复

问题解决:NestJS文件处理后错误场景下文件无法删除的问题

核心原因

  1. res.download异步未等待:原代码中res.download是回调式异步操作,未等待其执行完成就触发删除逻辑,错误场景下可能文件仍在处理/响应传输中就被尝试删除,或因响应未处理完毕导致后续逻辑异常。
  2. 删除逻辑重复且时机混乱:catch块中直接执行删除,但部分错误可能发生在文件下载阶段,此时部分目标文件尚未创建,删除逻辑会抛出不必要的错误;同时未统一处理所有流程分支的资源清理。

解决方案

1. 用finally块统一处理资源清理

无论流程成功还是失败,finally块都会执行,避免重复编写删除逻辑,确保所有场景下的文件都能被清理。

2. 封装res.download为Promise并等待完成

将Express的回调式res.download转为Promise,等待响应完全发送给客户端后再执行删除,防止文件还在被下载就被删除。

3. 完善错误响应逻辑

在catch块中先判断响应是否已发送,再发送错误信息,避免出现Cannot set headers after they are sent的异常。

修改后的代码示例

控制器方法

@Post(':id/export')
async potrfolioExport(
  @Body() files: string[],
  @Param('id') id: any,
  @Res() res: Response
) {
  const paths = files.map((elem) => `./uploads/${elem}.pptx`);
  const allFilesToDelete = [...paths, './uploads/template.pptx', './uploads/output.pptx'];

  // 把res.download封装为Promise,等待响应发送完成
  const sendDownload = () => new Promise<void>((resolve, reject) => {
    res.download('./uploads/output.pptx', (err) => {
      err ? reject(err) : resolve();
    });
  });

  try {
    await this.filesService.downloadFiles(files);
    await this.filesService.downloadAdditionalFile('template.pptx');
    await this.mergePres(paths);
    
    // 等待下载响应发送完成后再继续
    await sendDownload();
  } catch (error) {
    console.error("操作错误: ", error.body);
    // 避免重复发送响应
    if (!res.headersSent) {
      res.status(500).json({ message: error.message, error });
    }
  } finally {
    // 无论成功失败,最终执行文件删除
    try {
      await this.filesService.deleteFiles(allFilesToDelete);
    } catch (deleteErr) {
      console.error("文件删除失败: ", deleteErr.message);
      // 删除失败不影响响应结果,仅记录日志
    }
  }
}

deleteFiles方法优化(可选)

将同步的fs.existsSync改为异步方法,避免阻塞事件循环:

async deleteFiles(filePaths: string[]): Promise<void> {
  const unlink = fs.promises.unlink;
  const access = fs.promises.access;

  for (const filePath of filePaths) {
    try {
      // 异步判断文件是否存在
      await access(filePath, fs.constants.F_OK);
      await unlink(filePath);
      console.log(`${filePath} deleted`);
    } catch (error) {
      console.log(`${filePath} 不存在或删除失败: ${error.message}`);
    }
  }
}

关键说明

  • finally块确保所有流程分支都会触发文件删除,彻底避免资源泄漏。
  • 等待res.download完成后再执行后续逻辑,保证客户端已拿到文件后再删除服务器端的副本。
  • 增加res.headersSent判断,防止因响应已发送导致的二次响应错误。

内容的提问来源于stack exchange,提问作者Илья Никитин

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 06:55:19