#100DaysOfMERN - Day 56

#100DaysOfMERN - Day 56

·

4 min read

✏ File System API with Promises

For a long time, the fs module could only be used with callbacks (unless you made the effort and wrapped the fs methods with a custom function to return a promise, or used some library, or since Node.js v8, 'util.promisify()').

Since Node.js v10, this functionality has become part of the API, accessible through fs/promises. In this post, I'll show an example function that I've used while handling files and filenames, in both the old and the new version.

Requiring both:

const fs = require('fs');
const fsp = require('fs/promises');

✏ Renaming all Files in a Directory

I'm still working on my Radio App, and one thing that needed to be done was to normalise the filenames, so they all follow a certain pattern. Your use case might be different, the point is to show how to use the API for read/write operations.

Without Promises

First, getting access to the filenames in a certain folder:

function updateFileNames(folderPath) {
    fs.readdir(folderPath, (err, fileNames) => {
        if (err) {
            console.log('ERR IN READDIR: ', err);
        }

        console.log(fileNames);
        process.exit()
    });
}

updateFileNames('C:/Users/jsdisco/music')

The .rename method takes the old (absolute) path, the new path, and a callback. So the next step is to create an array that holds both paths. For the sake of simplicity, I'll just add an incrementing ID to the filename:

function updateFileNames(folderPath) {
    fs.readdir(folderPath, (err, fileNames) => {
        if (err) {
            console.log('ERR IN READDIR: ', err);
        }

        const filePaths = fileNames.map((oldName, i) => {
            const newName = `${i}-${oldName}`;

            const oldPath = `${folderPath}/${oldName}`;
            const newPath = `${folderPath}/${newName}`;

            return [oldPath, newPath];
        });

        console.log(filePaths);
        process.exit()
    });
}

updateFileNames('C:/Users/jsdisco/music')

Be mindful that when working with large amounts of files, and maybe performing some complicated regex on the names, it makes sense to double-check if you really have the right paths...

(note that you'll get no error if oldPath doesn't lead to an existing file, the code will just silently fail)

Now for the .rename method. Since it's an asynchronous operation, and you can't use async/await, you can't just loop over the filenames and rename them all at once. So I wrote a recursive function to solve this:

function renameFiles(pathsArr) {

    // base case: exit script
    if (pathsArr.length === 0) {
        process.exit();
    }

    // get the paths from first item and remove first item
    let [oldPath, newPath] = pathsArr[0];
    arr.shift();

    // rename files and call function again with new array
    fs.rename(oldPath, newPath, err => {
        if (err) {
            console.log('ERR IN RENAME: ', err);
        }
        return renameFiles(pathsArr);
    });
}

The complete script for renaming all files in a folder:

const fs = require('fs');

function updateFileNames(folderPath) {
    fs.readdir(folderPath, (err, fileNames) => {
        if (err) {
            console.log('ERR IN READDIR: ', err);
        }

        const filePaths = fileNames.map((oldName, i) => {
            const newName = `${i}-${oldName}`;

            const oldPath = `${folderPath}/${oldName}`;
            const newPath = `${folderPath}/${newName}`;

            return [oldPath, newPath];
        });

        renameFiles(filePaths);
    });
}

updateFileNames('C:/Users/jsdisco/music')

With Promises

The situation is considerably simpler since you can now loop with .forEach over the paths array, so there's no need for a recursive helper function. The whole script:

const fsp = require('fs/promises');

async function updateFileNamesWithPromises(folderPath) {
    try {
        // get the file names
        const fileNames = await fsp.readdir(folderPath);

        // map to create array with old and new paths
        const filePaths = fileNames.map((oldName, i) => {
            const newName = `${i}-${oldName}`;

            const oldPath = `${folderPath}/${oldName}`;
            const newPath = `${folderPath}/${newName}`;

            return [oldPath, newPath];
        });

        // rename files in async forEach loop
        filePaths.forEach(async ([oldPath, newPath]) => {
            await fsp.rename(oldPath, newPath);
        });

    } catch (err) {
        console.log('ERR IN READDIR: ', err);
    }

    process.exit();
}

✏ Ressources

From callbacks to fs/promises to handle the file system in Node.js


✏ Thanks for reading!

I do my best to thoroughly research the things I learn, but if you find any errors or have additions, please leave a comment below, or @ me on Twitter. If you liked this post, I invite you to subsribe to my newsletter. Until next time 👋


✏ Previous Posts

You can find an overview of all previous posts with tags and tag search here:

#100DaysOfMERN - The App