John Abraham
John Abraham

Reputation: 18821

Return final value from traversing folder function

Question: Why won't a var things return a value from outside the walk() function? And how do I fix it?

Hypothosis: this is async and the console.log is happening too early. Which would lead me to how can I make this a Promise (i'm using node 4.1.1)

var walk = function(dir, done) {
  var results = [];
  fs.readdir(dir, function(err, list) {
    if (err) return done(err);
    var i = 0;
    (function next() {
      var file = list[i++];
      if (!file) return done(null, results);
      file = dir + '/' + file;
      fs.stat(file, function(err, stat) {
        if (stat && stat.isDirectory()) {
          walk(file, function(err, res) {
            results = results.concat(res);
            next();
          });
        } else {
          results.push(file);
          next();
        }
      });
    })();
  });
};

function traverseDirectories() {
    var things = walk('src/', function(err, results){
        if(err) throw err;
        console.log(results) // ['dir/thing.png', 'dir/thing2.png']
        return results;
    });
    console.log(things) // undefined
};
traverseDirectories();

Upvotes: 2

Views: 59

Answers (1)

Buzinas
Buzinas

Reputation: 11735

Q. Why won't a var things return a value from outside the walk() function?

R. Because walk doesn't return anything (take a look and you''ll see that it's a void function).


Even if you make it a Promise, you won't be able to use it like:

var things = walk(...);
console.log(things);

Because Promises are thenable, and are still async, so it will be:

walk(...).then(function(things) {
  // do something with things here
});

To do what you want, you would need something that doesn't exist in current Javascript yet.

There is an ES7 proposal of native async/await that will be a callback heaven, but atm, you can use:

  • Async/Await library (It's an amazing library, but very far from native, and performance isn't cool)
  • ES7 transpiler - you can write the ES7 code today, and it will transpile for you to ES5 (e.g Babel)

But, if you're already using the newest version of NodeJS (4.0.0 as the time of writing) - and if you're not, you really should - the best way of achieving what you want is to use generators.

Combined with a small library named co, it will help you to achieve almost what the ES7 async/await proposes, and it will mostly use native code, so both readability and performance are really good:

var co = require('co');

var traverseDirectories = co(function *traverseDirectories() {
  var things = yield walk('src/');
  console.log(things) // there we go!
});

function walk(dir, results) {
  return new Promise(function(resolve, reject) {
    fs.readdir(dir, function(err, list) {
      if (err)
        reject(err);

      var i = 0;
      (function next() {
        var file = list[i++];
        if (!file) resolve(results);
        file = dir + '/' + file;
        fs.stat(file, function(err, stat) {
          if (stat && stat.isDirectory()) {
            walk(file).then(function(res) {
              results = results.concat(res); 
              next();
            });
          } else {
            results.push(file);
            next();
          }
        });
      })();
    });
  });
}

You can read more about this subject in this awesome Thomas Hunter's blog post.

Upvotes: 1

Related Questions