Check if folder exists in node, asynchronously with promises, native.

Not only that, pretty much all fs methods are available with a promises interface.

Since Node v10.0.0 fs has experimental promises support, to test that, we want to see if a folder exists and is accessible. Wi will start from the Synchronous operation, and we will end up taking advantage of async await to deal with the promises. Only one thing to note: this is flagged as experimental, and we will get something like: (node:578) ExperimentalWarning: The fs.promises API is experimental .

The setup

We will use the fs.stat method, in all it’s different flavours if the path does not exist or is not accessible, you get an error: ENOENT. Also make sure your environment is running on node v10 or older. v10 goes LTS in just a few months, and in node v10 you can use fs with promises.

First let’s make the directory and file structure we will test.

mkdir -p uploads/memes
echo "some content for the file">uploads/memes/not-folder
touch stat.js

Open stat.js and import right away both versions of fs. Also set the paths to be tested, we want to check the following 3 cases:

  • 1. A valid directory
  • 2. A folder does not exist (fs.stat should throw an error)
  • 3. A file; path resolves to something but it is not a dir
const fs = require("fs");
const fsPromises = require("fs").promises;

const pathsToTest = [
    "uploads/memes",
    "nopes/memes",
    "uploads/memes/not-folder",
];

function isErrorNotFound(err) {
    return err.code === "ENOENT";
}

Also we will define an error checker function that will be reused by all the methods we build.

Before we start with the actual methods we will setup looping functions that will pick up the array of paths and ask the given method if each of the paths is a directory, then print the method used and the result. For each fs.stat variation we will use a slightly different looper, but the principle stands.

The simple way: fs.statSync

function isFolder_sync(path) {
    try {
        const stat = fs.statSync(path);
        return stat.isDirectory();
    } catch (err) {
        // if it's simply a not found error
        if (isErrorNotFound(err)) {
            return false;
        }
        //otherwise throw the error
        throw err;
    }
}

/** Looper function */
function checkPathsSync(method) {
    console.log(`${method.name}: `, ...pathsToTest.map(path => method(path)));
}

checkPathsSync(isFolder_sync);
//isFolder_sync:  true false false

The core idea is: we call fs.statSync inside a try catch block to handle the case where the dir is not found, if the error is something else we don’t recognize, we still throw.

The classic Node: fs.stat (with a callback)

function isFolder_callback(path, callback) {
    fs.stat(path, (err, stat) => {
        if (err) {
            const errorToReturn = isErrorNotFound(err) ? undefined : err;
            callback(errorToReturn, false);
            return;
        }
        callback(undefined, stat.isDirectory());
    });
}

function checkPaths(method) {
    pathsToTest.forEach(path =>
        method(path, (err, result) =>
            console.log(`${method.name}: `, err ? err : result)
        )
    );
}

checkPaths(isFolder_callback);
// isFolder_callback:  true
// isFolder_callback:  false
// isFolder_callback:  false

Callbacks will catch errors, so no need for the try catch. Personally I find Callbacks very pretty in simple use cases.

The new approach: promises enabled fs.stat

function isFolder_promise(path) {
    return fsPromises
        .stat(path)
        .then(fsStat => {
            return fsStat.isDirectory();
        })
        .catch(err => {
            if (isErrorNotFound(err)) {
                return false;
            }
            throw err;
        });
}

function checkPromisedPaths(method) {
    Promise.all(pathsToTest.map(path => method(path))).then(values => {
        console.log(`${method.name}: `, values);
    });
}

checkPromisedPaths(isFolder_promise);
// (node:877) ExperimentalWarning: The fs.promises API is experimental
// isFolder_promise:  [ true, false, false ]

Same idea as before, catch the not found error, if anything else is caught, throw a fuss with the error.

To wrap up: Async Await.

beacause, why not?

async function isFolder_asyncAwait(path) {
    // the result can be either false (from the caught error) or it can be an fs.stats object
    const result = await fsPromises.stat(path).catch(err => {
        if (isErrorNotFound(err)) {
            return false;
        }
        throw err;
    });

    return !result ? result : result.isDirectory();
}

checkPromisedPaths(isFolder_asyncAwait);
// isFolder_asyncAwait:  [ true, false, false ]

This was a fun actual implementation of async await, I had not used it so far, beyond the typical hello world.

What can be improved?

well, the isErrorNotFound method should be taking care of throwing a fit if the path is not finding anything. Also I’m sure I can come up with more elegant versions of the implementations, but I’m happy with this as a POC.

The whole code can be found as a gist at: gist.github.com/baamenabar/d639055caaacd5dbd139c302caf8a4f9

Resources

Main resource is the node docs: nodejs.org/api/fs.html#fs_fs_promises_api …aaand of course MDN.

Published :

Last modified :

Comments

comments powered by Disqus