Nodejs: deleting files after all operations are completed

69 views Asked by At

In NestJS controller, I have a function that downloads files, saves them to the server's files, performs specific operations on these files, and then returns the processed file to the client. After all the operations are complete, I need to delete all the downloaded files.Everything works fine, but if some error occurs, the files are not deleted. I tried adding a delete function to the catch and finally block, but as I understand, it works before all operations in the try block are completed and the files are not deleted. How can I fix this?

  @Post(':id/export')
  async potrfolioExport(
    @Body() files: string[],
    @Param('id') id: any,
    @Res() res: Response
  ) {

    const paths = files.map((elem) => `./uploads/${elem}.pptx`);

    try {
      await this.filesService.downloadFiles(files);
  
      await this.filesService.downloadAdditionalFile('template.pptx');

      await this.mergePres(paths);
      res.download(`./uploads/output.pptx`);

      await this.filesService.deleteFiles([...paths, './uploads/template.pptx', './uploads/output.pptx']);
    } catch (error) {
      console.error("Error: ", error.body);
      await this.filesService.deleteFiles([...paths, './uploads/template.pptx', './uploads/output.pptx']);
      res.status(500).json({ message: error.message, error: error });
    }
   
  }
  async deleteFiles(filePaths: string[]): Promise<void> {
    try {
      const unlink = util.promisify(fs.unlink);

      for (const filePath of filePaths) {

        if (fs.existsSync(filePath)){
          await unlink(filePath);
          console.log(`${filePath} deleted`);
        } else {
          console.log(`${filePath} does not exist`);
        }
      }
    } catch (error) {
      console.error(`Deleting Error: ${error.message}`);
      throw new Error(`Deleting presentation Error: ${error.message}`);
    }
  }
1

There are 1 answers

0
jfriend00 On

You aren't waiting for res.download() to complete so you're trying to delete ./uploads/output.pptx before you've finished sending it to the client. res.download() takes an optional callback that will tell you when it's complete or you can promisify it so that you can then await it and then only call deleteFiles() when res.download() has finished.

For example, you could do this:

@Post(':id/export')
async potrfolioExport(
    @Body() files: string[],
    @Param('id') id: any,
    @Res() res: Response
) {

    const paths = files.map((elem) => `./uploads/${elem}.pptx`);

    try {
        await this.filesService.downloadFiles(files);
        await this.filesService.downloadAdditionalFile('template.pptx');
        await this.mergePres(paths);
        res.download(`./uploads/output.pptx`, (error) => {
            if (error) {
                if (!res.headersSent) {
                    res.status(500).json({ message: error.message, error: error });
                }
            }
            this.filesService.deleteFiles([...paths, './uploads/template.pptx', './uploads/output.pptx']);
        });

    } catch (error) {
        console.error("Error: ", error.body);
        await this.filesService.deleteFiles([...paths, './uploads/template.pptx', './uploads/output.pptx']);
        res.status(500).json({ message: error.message, error: error });
    }    
}

Or, here's a version where you promisify res.download() and consolidate error handling:

import { promisify } from 'node:util';

@Post(':id/export')
async potrfolioExport(
    @Body() files: string[],
    @Param('id') id: any,
    @Res() res: Response
) {

    const paths = files.map((elem) => `./uploads/${elem}.pptx`);

    try {
        await this.filesService.downloadFiles(files);
        await this.filesService.downloadAdditionalFile('template.pptx');
        await this.mergePres(paths);

        res._download = promisify(res.download);
        await res._download((`./uploads/output.pptx`);
    } catch (error) {
        console.error("Error: ", error.body);
        if (!res.headersSent) {
            res.status(500).json({ message: error.message, error: error });
        }
    } finally {
        this.filesService.deleteFiles([...paths, './uploads/template.pptx', './uploads/output.pptx']).catch(err => {
            console.error(err);    
        });    
    }    
}