4

I want to log all the files in the directory recursively and return a confirmation when all files are logged. Here's the directory structure.

sample │ app.js └───Temp1 │ │ temp1.js │ └───Temp2 │ │ temp2.js

Here's the code

```

let readDirectory = function(dirname){
    return new Promise((resolve,reject)=>{
         fs.readdir(dirname,(err,files)=>{
            if(err) reject(err);
            files.forEach(file=>{
                fs.stat(`${dirname}/${file}`,(err,stats)=>{
                    if(stats.isDirectory()){
                        readDirectory(`${dirname}/${file}`)
                    }else{
                        resolve(console.log(file));                      
                    }
                })                
            })
        })
    })   
}



readDirectory(sampledir).then(()=>console.log('completed'));

```

Below is the result when I execute this function.

```

app.js
completed
temp1.js
temp2.js

```

Where should I resolve in order to get output as below.

```

app.js
temp1.js
temp2.js
completed

```

2 Answers2

3

You need to resolve only after Promise.all resolves over every file. Also, you need to resolve each directory promise only after each file in the directory is finished:

let readDirectory = function(dirname) {
  return new Promise((resolveAll, rejectAll) => {
    fs.readdir(dirname, (err, files) => {
      if (err) rejectAll(err);
    })
  }).then((files) => {
    const filesPromises = files.map(file => (
      new Promise((resolveFile, rejectFile) => {
        fs.stat(`${dirname}/${file}`, (err, stats) => {
          if (err) rejectFile(err);
          if (stats.isDirectory()) {
            readDirectory(`${dirname}/${file}`)
             .then(resolveFile);
             .catch(rejectFile);
          } else {
            resolveFile(console.log(file));
          }
        })
      })
    ));
    return Promise.all(filesPromises).then(resolveAll);
  });
}

Note that this is pretty difficult to make sense of - you'll be far better off using async/await instead, something like this:

let readDirectory = async function(dirname) {
  const files = await new Promise((resolve, reject) => {
    fs.readdir(dirname, (err, files) => {
      if (err) reject(err);
      resolve(files);
    });
  });
  const filesPromises = files.map(async (file) => {
    const stats = await new Promise((resolve, reject) => {
      fs.stat(`${dirname}/${file}`, (err, stats) => {
        if (err) reject (err);
        else resolve(stats);
      });
    });
    if (stats.isDirectory()) await readDirectory(`${dirname}/${file}`);
    else console.log(file);
  });
  return Promise.all(filesPromises);
}
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • suggestion `const { promisify } = require('util')` – Endless May 13 '18 at 09:05
  • Avoid the [`Promise` constructor antipattern](https://stackoverflow.com/q/23803743/1048572?What-is-the-promise-construction-antipattern-and-how-to-avoid-it) in the first snippet! – Bergi May 13 '18 at 10:17
  • @Bergi Given that both `readdir` and `fs.stat` (natively) use callbacks rather than promises, if I want the main function to return a `Promise` and if I want to be able to use `Promise.all` to check that all the files are done, don't I have to use the Promise constructor in both cases? What did I miss? – CertainPerformance May 13 '18 at 19:29
  • @CertainPerformance You have to use the `new Promise` constructor twice, that's fine, but you must not nest it! Notice that you forgot to call `rejectAll`. Do `resolve(files)`, and put the rest of the code in a `then` callback. Same for `resolve(stats)`. – Bergi May 13 '18 at 19:34
  • @Bergi Ah, I see what you mean. Yes, you're right - thanks! – CertainPerformance May 13 '18 at 19:36
  • Thanks, you still forgot to fix the `fs.stat` call where `rejectFile` is not called in the recursive case. Use `then` here as well, only resolve with the `files`. – Bergi May 13 '18 at 20:16
3

If you're using Node 8 or later, you can promisify to improve readability:

const util = require('util')
const fs = require('fs')

const stat = util.promisify(fs.stat)
const readdir = util.promisify(fs.readdir)

let readDirectory = (dirname) =>
    readdir(dirname).then(files =>
      Promise.all(files.map(file =>
          stat(`${dirname}/${file}`).then(stats =>
            stats.isDirectory() ? readDirectory(`${dirname}/${file}`) :
            console.log(file)
          )
      ))
    )

readDirectory(__dirname).then(() => {
    console.log('Completed')
})

Or a super simplified version with async/await:

let readDirectory = async dirname =>
    await Promise.all((await readdir(dirname)).map(async file =>
       (await stat(`${dirname}/${file}`)).isDirectory() ?
            readDirectory(`${dirname}/${file}`) : console.log(file)
    ))
calbertts
  • 1,513
  • 2
  • 15
  • 34