18

I am trying to create an array of specific files in a directory; which will go through a few test cases to make sure it fits a given criteria.

I'm using the fs.readdir method, but it doesn't return a promise meaning I cannot push to an array.

My idea was to populate an array (arr) with the files I actually want to output and then do something with that array. But because readdir is asynchronous and I can't chain a .then() onto it, my plans are quashed.

I've also tried the same thing with readdirSync to no avail.

const Promise = require('bluebird');
const fs = Promise.promisifyAll(require('fs'));

var arr = [];

fs.readdirAsync(folder).then( files => {
  files.forEach(file => {
    fs.stat(folder + '/' + file, (err, stats) => {
       if(!stats.isDirectory()) {
         arr.push(file);
        return;
      }
     });
   });
})
.then( () => {
  console.log(arr);
});
grpcMe
  • 1,367
  • 5
  • 15
  • 28
  • Why can't you use a callback? --- May you edit your question to include a [mcve] of your efforts? – evolutionxbox May 17 '17 at 08:23
  • _"I've also tried the same thing with readdirSync to no avail"_. That function will _return_ an array of filenames, which seems to me is exactly what you're looking for? – robertklep May 17 '17 at 08:34
  • 1
    Have added an example of what I'm trying to do. `arr` returns `[]` – grpcMe May 17 '17 at 09:08

8 Answers8

17

fs.readdir is callback based, so you can either promisify it using bluebird or Node.js util package (or writing a simple implementation of it yourself), or simply wrap the call in a promise, like so:

// Wrapped in a promise
new Promise((resolve, reject) => {
    return fs.readdir('/folderpath', (err, filenames) => err != null ? reject(err) : resolve(filenames))
})

Or the custom promisify function:

// Custom promisify
function promisify(fn) {
  /**
   * @param {...Any} params The params to pass into *fn*
   * @return {Promise<Any|Any[]>}
   */
  return function promisified(...params) {
    return new Promise((resolve, reject) => fn(...params.concat([(err, ...args) => err ? reject(err) : resolve( args.length < 2 ? args[0] : args )])))
  }
}

const readdirAsync = promisify(fs.readdir)
readdirAsync('./folderpath').then(filenames => console.log(filenames))
  • I have tried adding a promise, but the `arr` array still returns `[]`. I've added an example which will hopefully clear up what I'm doing. This is the first time using `readddir` so it's entirely possible I'm using it wrong. – grpcMe May 17 '17 at 09:09
  • I had to do err !== null, but this worked like a charm, thanks! – Max Apr 03 '20 at 11:14
  • ```err ? reject(err) : resolve(filenames)``` => made the first code functioning since err returned null for me! – oae May 23 '20 at 06:26
  • Oh shoot, not sure why I did `!== undefined`, the default behaviour is returning `null`... Updated my answer – kristofferostlund May 25 '20 at 06:45
12

Just plain javascript, no libs:

function foo (folder, enconding) {
    return new Promise(function(resolve, reject) {
        fs.readdir(folder,enconding, function(err, filenames){
            if (err) 
                reject(err); 
            else 
                resolve(filenames);
        });
    });
};

e.g

foo(someFolder, "someEncoding")
.then((files) => console.log(files))
.catch((error) => console.log(error));
Marcus Fonseca
  • 871
  • 9
  • 8
5

I figured it out; I just needed to use statSync instead of stat

const fs = require('fs');

var arr = [];

var files = fs.readdirSync(folder);

files.forEach(file => {
  let fileStat = fs.statSync(folder + '/' + file).isDirectory();
  if(!fileStat) {
    arr.push(file);
  }
});

console.log(arr);
grpcMe
  • 1,367
  • 5
  • 15
  • 28
4

You can now use ES6 destructuring assignment (documentation) :

const
    fs = require('fs'),
    FILES = [...fs.readdirSync('src/myfolder')];

console.log(FILES);
PedroZorus
  • 661
  • 1
  • 6
  • 21
3

As of Node.js v10, there is an fs.promises API that supports this:

const fs = require('fs');

var arr = [];

fs.promises.readdir(folder).then( files => {
  files.forEach(file => {
    fs.stat(folder + '/' + file, (err, stats) => {
       if(!stats.isDirectory()) {
         arr.push(file);
        return;
      }
     });
   });
})
.then( () => {
  console.log(arr);
});
Dave Bauman
  • 9,252
  • 1
  • 19
  • 19
2

if you want to use a promise instead of a callback you can promisify fs.

const Promise = require('bluebird');
const fs = Promise.promisifyAll(require('fs'));

fs.readdirAsync('./some').then()

http://bluebirdjs.com/docs/api/promise.promisifyall.html

Karim
  • 8,454
  • 3
  • 25
  • 33
  • Have just added an example of what I'm trying to do - in the last `.then()` the `arr` returns as `[]` – grpcMe May 17 '17 at 09:06
1

Have you tried fs-extra module?

Kristian
  • 1,058
  • 1
  • 11
  • 16
1
new Promise((resolve, reject) => {
    return fs.readdir('/folderpath', (err, filenames) => err ? reject(err) : resolve(filenames))
})

Do not err !== undefined because err is actually null!!

pautasso
  • 135
  • 1
  • 4
  • You can actually simplify it even more. As you only have one statement and you're using an arrow function, you can remove the braces and the 'return' and it will work just fine. – Javi Marzán May 18 '20 at 15:33