Home > front end >  Why await within async function doesn't work for fs modules?
Why await within async function doesn't work for fs modules?

Time:01-06

I am trying to read a sample.json file through my js code. First my program checks for sample.json within every folder in the specified path. And it reads the sample.json if available and fetches the data. But the await used doesn't work as expected and simply passes the empty object to the calling function before the async functions completes it execution. I have attached the image for the issue.

    async function getAvailableJson(filesPath) {
    let detectedJson = {};
    let folders = await fs.promises.readdir(filesPath);
    folders.forEach(async function(folder) {
        await fs.promises.access(path.join(filesPath, folder, "Sample.json")).then(async function() {
                jsonData = await fs.promises.readFile(path.join(filesPath, folder ,"Sample.json"))
                const directory = JSON.parse(jsonData)
                const hashvalue = Hash.MD5(jsonData)
                detectedJson[directory["dirName"]] = {
                                    name: directory["dirName"],
                                    version: directory["dirVersion"],
                                    hash: hashvalue
                                }
                                console.log(detectedJson);
                            }).catch(function(err) {
                                if(err.code === "ENOENT")
                                {}
                            });
                    });

    return detectedJson;
}

I don't want to use any sync functions since it creates unnecessary locks. I have also tried with fs.readdir, fs.access and fs.readFile functions. Could someone point out what I am doing wrong here since I am new to Node.js thanks in advance.

Sample Image

CodePudding user response:

Change your .forEach() to use for/of instead and generally simplify by not mixing await and .then().

async function getAvailableJson(filesPath) {
    let detectedJson = {};
    let folders = await fs.promises.readdir(filesPath);
    let detectedJson = {};
    for (let folder of folders) {
        let file = path.join(filesPath, folder, "Sample.json");
        try {
            let jsonData = await fs.promises.readFile(file);
            const directory = JSON.parse(jsonData);
            const hashvalue = Hash.MD5(jsonData);
            detectedJson[directory["dirName"]] = {
                name: directory["dirName"],
                version: directory["dirVersion"],
                hash: hashvalue
            };
        } catch (err) {
            // silently skip any directories that don't have sample.json in them
            // otherwise, throw the error to stop further processing
            if (err.code !== "ENOENT") {
                console.log(`Error on file ${file}`, err);
                throw err;
            }
        }
        console.log(detectedJson);
    }
    return detectedJson;
}

Summary of Changes:

  1. Replace .forEach() with for/of.
  2. Remove .then() and use only await.
  3. Remove .catch() and use only try/catch.
  4. Remove call to fs.promises.access() since the error can just be handled on fs.promises.readFile()
  5. Add logging if the error is not ENOENT so you can see what the error is and what file it's on. You pretty much never want to silently eat an error with no logging. Though you may want to skip some particular errors, others must be logged. Rethrow errors that are not ENOENT so the caller will see them.
  6. Declare and initialize all variables in use here as local variables.

.forEach() is not promise-aware so using await inside it does not pause the outer function at all. Instead, use a for/of loop which doesn't create the extra function scope and will allow await to pause the parent function.

Also, I consider .forEach() to be pretty much obsolete these days. It's not promise-aware. for/of is a more efficient and more generic way to iterate. And, there's no longer a need to create a new function scope using the .forEach() callback because we have block-scoped variables with let and const. I don't use it any more.

Also, I see no reason why you're preflighting things with fs.promises.access(). That just creates a race condition and you may as well just handle whatever error you get from fs.promises.readFile() as that will do the same thing without the race condition.


See also a related answer on a similar issue.

  •  Tags:  
  • Related