noobinmath
noobinmath

Reputation: 185

Node js async await functions doesn't wait each other

I have a project that has functions that read files and extract their hash code. After these hash codes are extracted in the project, subfiles are built one by one. Finally, what I want to do is to throw all these hash codes into an array and create a json file. I need to do this after the IterateFolders() function has run and finished in readDirectory function. But console.log is running on a bottom line without waiting for this function, please help.

My functions are as follows:


//Calculate build time 
function getBuildTime(start,end) {
    let time = (end - start);
    let buildTime = `${new Date().toLocaleDateString()} ${new Date().toLocaleTimeString()} Build time: ${time} ms \n`   
    
    fs.writeFile('build-time.log', buildTime,function (err) { //output log file
        if (err) return console.log(err);
      });
}

//async metaHash calculation from folder path
async function computeMetaHash(folder, inputHash = null) {
    const hash = inputHash ? inputHash : createHash('sha256');
    const info = await fsp.readdir(folder, { withFileTypes: true });
    //construct a string from the modification date, the filename and the filesize
    for (let item of info) {
        const fullPath = path.join(folder, item.name)
        if (item.isFile()) {
            const statInfo = await fsp.stat(fullPath); //stat return all informations about file
            // compute hash string name:size:mtime
            const fileInfo = `${fullPath}:${statInfo.size}:${statInfo.mtimeMs}`;
            hash.update(fileInfo);        
        } else if (item.isDirectory()) {
            // recursively walk sub-folders
            await computeMetaHash(fullPath, hash);
        }
    }
    // if not being called recursively, get the digest and return it as the hash result
    if (!inputHash) {
        return hash.digest('base64');
    }
}


async function iterateFolders(folderPath) {
    folderPath.forEach(function (files) {
        //function takes folder path as inputh
        computeMetaHash(files).then(result => { //call create hash function
        
            console.log({"path":files,"hashCode":result});

        }).then(()=>{ //build fragments 
            //The files is array, so each. files is the folder name. can handle the folder.
            console.log("%s build...", files);
            execSync(`cd ${files} && npm run build`, { encoding: 'utf-8' });  

        }).then(()=>{// Finish timing

            end = new Date().getTime();
            getBuildTime(start,end);  

        }).catch(err => {
            console.log(err);
        });
    }); 

}

async function readDirectory() {

    let files = await readdir(p)
    const folderPath = files.map(function (file) {
        //return file or folder path
        return path.join(p, file);
    }).filter(function (file) {
        //use sync judge method. The file will add next files array if the file is directory, or not. 
        return fs.statSync(file).isDirectory();
    })
    //check hash.json exist or not
    if (fs.existsSync(hashFile)) {
        // path exists
        console.log("File exists: ", hashFile);
        } 
    else 
        {
            //This is the first pipeline, all fragments will build then hash.json will created.
            console.log(hashFile," does NOT exist, build will start and hash.json will created:");
            // Start timing
            start = new Date().getTime();
            iterateFolders(folderPath,files);

            console.log("IT WILL BE LAST ONE ")
            
        }
}
readDirectory();

Upvotes: 0

Views: 365

Answers (2)

Elie G.
Elie G.

Reputation: 1723

In the iterateFolders function, you need to await computeMetaHash calls. To do so you can either use a for loop instead of calling forEach on folderPath or change forEach to map and use Promise.all.

Using the for loop method (synchronous):

async function iterateFolders(folderPath) {
    for (let files of folderPath) {
        //function takes folder path as inputh
        await computeMetaHash(files).then(result => { //call create hash function
            console.log({"path":files,"hashCode":result});

        }).then(()=>{ //build fragments 
            //The files is array, so each. files is the folder name. can handle the folder.
            console.log("%s build...", files);
            execSync(`cd ${files} && npm run build`, { encoding: 'utf-8' });  

        }).then(()=>{// Finish timing

            end = new Date().getTime();
            getBuildTime(start,end);  

        }).catch(err => {
            console.log(err);
        });
    }
}

Using the Promise.all method (asynchronous):

async function iterateFolders(folderPath) {
    return Promise.all(folderPath.map(function (files) {
        //function takes folder path as inputh
        return computeMetaHash(files).then(result => { //call create hash function
        
            console.log({"path":files,"hashCode":result});

        }).then(()=>{ //build fragments 
            //The files is array, so each. files is the folder name. can handle the folder.
            console.log("%s build...", files);
            execSync(`cd ${files} && npm run build`, { encoding: 'utf-8' });  

        }).then(()=>{// Finish timing

            end = new Date().getTime();
            getBuildTime(start,end);  

        }).catch(err => {
            console.log(err);
        });
    }));
}

If you prefer, using async/await also allows you to get rid of the then and catch in both methods which I believe makes it a little easier to read and understand.
Here's an example using the Promise.all method:

async function iterateFolders(folderPath) {
  return Promise.all(folderPath.map(async (files) => {
      try {
          const result = await computeMetaHash(files);
          console.log({ path: files, hashCode: result });

          // build fragments

          //The files is array, so each. files is the folder name. can handle the folder.
          console.log('%s build...', files);
          execSync(`cd ${files} && npm run build`, { encoding: 'utf-8' });

          
          // Finish timing
          const end = Date.now();
          getBuildTime(start, end);
          
      } catch(err) {
          console.log(err);
      }
  }));
}

You might also want to check out for await... of

Note: you also need to await iterateFolders when it's called in readDirectory.

Upvotes: 0

Andrey Popov
Andrey Popov

Reputation: 7510

Well if you want to wait for its execution, then you have to use await :) Currently it's just iterateFolders(folderPath,files);, so you run it, but you don't wait for it.

await iterateFolders(folderPath,files);

That's your first issue. Then this method runs some loop and calls some other methods. But first async-await needs to return a promise (which you do not do). And second - it doesn't work in forEach, as stated in the comments above. Read Using async/await with a forEach loop for more details.

Fix those three issues and you'll make it.

Upvotes: 2

Related Questions