How to Delete Multiple Files in NodeJS using Promises

29/05/2020 | 4 minute read | Tags: NodeJS

Deleting Files Overview

NodeJS is a great tool and can do many things but it wasn't until today that I found out you can use it to delete files. I have known for a while you can use it create files by writing to a destination using a Write Stream or another method for writing files. However, deleting files had remained a mystery to me until today. Here's how you can do it as well as how to delete multiple ones using promises.

To accomplish this seemingly mundane task we are going to use the fs.unlink() method from nodeJS' File System package, here is the syntax for the method:

fs.unlink(path, callback)

So, all we need to do is call fs.unlink(), pass in the path to the file you want to delete and then pass a callback to be called after the file is deleted or the process errors out.

The NodeJS documentation for this method has a great example of how we can use it:

// Assuming that 'path/file.txt' is a regular file.
fs.unlink('path/file.txt', (err) => {
  if (err) throw err;
  console.log('path/file.txt was deleted');
});

Something a bit more advanced

Why have I been needing to delete files I hear you ask? Okay, you probably haven't been asking that but I'm going to tell you anyway.

As everyone and their dog has been creating COVID-19 APIs, I too have decided to make one that can be used to query data regarding the pandemic and how it's impacting the different countries / territories around the world. If you're interested you can check out my project here but at the time of writing it's far from finished and is more of a learning project that anything production ready.

Anyway within this project, I needed to download several files parse them and merge them into one file. After this was done instead of just leaving these files lingering about I thought it was best to do some housekeeping and get rid of them. So, I started doing some researching and came across the above method.

However, I needed to do something a bit more... a bit more asynchronous.

So, here is how I deleted multiple files using fs.unlink and promises.

Promises

If you're interested in the full code snippet, please jump to the bottom of the article. If you're interested in how it works; keep reading.

The first thing we need to do is import our packages and then define a function for all our code to live in. But the interesting part is because we are deleting multiple files, we need to immediately return a Promise.all(). The reason we are doing is because while Promise.all() awaits all promises within it to be resolved, Promise.all() itself actually returns a promise so by immediately returning one we can await this container promise in any parent function(s).

Now, you'll see in the code below, I have hard coded in the values I needed to delete as the file names for my project will never change but if you did not know this information or they are dynamic you could get all the files in a directory and loop over them instead of hard-coding the array.

import fs from 'fs';

function dataDeleter() {
  return Promise.all(['confirmed', 'deaths', 'recovered', 'dailyReport'])
};

Now, we have that bit sorted, let's get into the meaty part. For each file we need to delete we need to return a new promise so Promise.all() has something to await on.

A quick side note but don't make the mistake I first made which is using .forEach() to loop over the array, instead use .map() because .forEach() will never return any values besides 'undefined' which is not helpful to us as we need promises returned. So, make sure to use .map() like so:

function dataDeleter() {
  return Promise.all(
    ['confirmed', 'deaths', 'recovered', 'dailyReport'].map(
      file =>
        new Promise((res, rej) => {
          // Promise code goes in here.
        })
    )
  )
};

So, we now have a new promise being returned for each of our values in the array by using the .map() method, all we need to do now is delete the files, resolve the promises and then do some basic error handling.

Let's take a look at deleting the files.

function dataDeleter() {
  return Promise.all(
    ['confirmed', 'deaths', 'recovered', 'dailyReport'].map(
      file =>
        new Promise((res, rej) => {
          try {
            fs.unlink(`./data/${file}.csv`, err => {
              if (err) throw err;
              console.log(`${file}.csv was deleted`);
            });
          }
        })
  ))
};

What we did here is wrap all our code in a try statement (don't worry the catch statement will follow) and then call our fs.unlink() method we talked about earlier. Because we are doing this for multiple files we can't hard code in the path to the file we want to delete like the example from the docs. So, instead we used the template literals from ES6 to allow us to pass in the name of the file we want to delete into the directory path where it will be located (this works because all files are in the same directory).

Following this, regardless if the code errors or succeeds, the callback function passed to fs.unlink() will be called. We pass the 'err' value through to the callback so we can throw an error should there be one. If there is no error then we just console log what file was deleted and move on.

Now, due to how my code in this project works we actually convert all of the csv files into there own JSON files before merging them into one file which means there's actually 2 files to delete per value, luckily I had the foresight to name them the same thing (more like laziness). So, it's just a case of adding in the other file extension I need to delete as another fs.unlink() call like so:

function dataDeleter() {
  return Promise.all(
    ['confirmed', 'deaths', 'recovered', 'dailyReport'].map(
      file =>
        new Promise((res, rej) => {
          try {
            fs.unlink(`./data/${file}.csv`, err => {
              if (err) throw err;
              console.log(`${file}.csv was deleted`);
            });
            fs.unlink(`./data/${file}.json`, err => {
              if (err) throw err;
              console.log(`${file}.json was deleted`);
              res();
            });
          }
        })
  ))
};

As you can see, in the callback for the second deletion we resolve the promise which in terms is then resolved in the original Promise.all() we returned at the start of the function. However, we're not done yet; we still have a couple of things to sort out.

There was one issue I had to get around, you see for the first three values (confirmed, deaths and recovered) they are all merged into one file so therefore the original files can be deleted but the forth value (dailyReport) that is not merged into the main file so we need to keep that one for some of our queries.

However, implementing this change was actually easy, all we had to do is run an if statement in between the two calls to fs.unlink() and check if the value currently being mapped over is 'dailyReport' and if so resolve the promise and return from the loop, if it isn't that value then carry on as normal.

function dataDeleter() {
  return Promise.all(
    ['confirmed', 'deaths', 'recovered', 'dailyReport'].map(
      file =>
        new Promise((res, rej) => {
          try {
            fs.unlink(`./data/${file}.csv`, err => {
              if (err) throw err;
              console.log(`${file}.csv was deleted`);
            });
            if (file === 'dailyReport') {
              res();
              return;
            }
            fs.unlink(`./data/${file}.json`, err => {
              if (err) throw err;
              console.log(`${file}.json was deleted`);
              res();
            });
          }
        })
  ))
};

With that out the way, all we need to do now is add some basic error handling with a catch statement like so:

function dataDeleter() {
  return Promise.all(
    ['confirmed', 'deaths', 'recovered', 'dailyReport'].map(
      file =>
        new Promise((res, rej) => {
          try {
            fs.unlink(`./data/${file}.csv`, err => {
              if (err) throw err;
              console.log(`${file}.csv was deleted`);
            });
            if (file === 'dailyReport') {
              res();
              return;
            }
            fs.unlink(`./data/${file}.json`, err => {
              if (err) throw err;
              console.log(`${file}.json was deleted`);
              res();
            });
          } catch (err) {
            console.error(err);
            rej(err);
          }
        })
    )
  );
}

During this catch statement we console.error any errors we receive during the execution of the function but this is also where we also reject any promises should we get an error.

All in all this code isn't to complex and I'm happy with how it turned out. I hope you also found this helpful and if you did I would appreciate you sharing this post with someone else who may also find it helpful.

If you have any questions, I'd be happy to answer them find my contact links down below.

,

Want More Content?

Join my weekly newsletter below.

Don't worry, I won't send you spam and you can unsubscribe at any time.