Search code examples
javascriptnode.jscallbackpromisees6-promise

Converting callbacks with for loop and recursion to promises


I wrote a function running recursively to find out files whose name include given world. I do not understand how promises works and cannot find a way to write this function with promises despite trying hard.

I tried returning a promise inside findPath function but I couldn't use it since extractFiles calls findPath. I tried to create a list of promises and return all but couldn't succeed neither.

So how could I write these functions with promises?

const fs   = require('fs');
const path = require('path');


function findPath(targetPath, targetWord, done) {
  if (!fs.existsSync(targetPath)) return;

  fs.readdir(targetPath, (err, allPaths) => {
    if (err) done(err, null);

    for (aPath of allPaths) {
      aPath = path.join(targetPath, aPath);
      extractFiles(aPath, targetWord, done);
    }
  });

  function extractFiles(aPath, targetWord, done) {
    fs.lstat(aPath, (err, stat) => {
      if (err) done(err, null);

      if (stat.isDirectory()) {
        findPath(aPath, targetWord, done);
      }
      else if (aPath.indexOf(targetWord) >= 0) {
        let fileName = aPath.split('.')[0];
        done(null, fileName);
      }
    });
  }
}

findPath('../modules', 'routes', file => {
  console.log(file);
});

Solution

  • Firstly, to make the "core" code more readable, I'd promisify the fs functions

    const promisify1p = fn => p1 => new Promise((resolve, reject) => {
        fn(p1, (err, result) => {
            if(err) {
                reject(err);
            } else {
                resolve(result);
            }
        });
    });
    const readdirAsync = promisify1p(fs.readdir);
    const lstatAsync = promisify1p(fs.lstat);
    

    Then, just chain the promises as you would with any other promises

    const fs = require('fs');
    const path = require('path');
    
    function findPath(targetPath, targetWord) {
        const readPath = target => 
            readdirAsync(target)
            .then(allPaths => 
                Promise.all(allPaths.map(aPath => extractFiles(path.join(target, aPath))))
                .then(x => x.filter(x=>x)) // remove all the "false" entries - i.e. that don't match targetWord
                .then(x => [].concat.apply([], x)) // flatten the result
            );
        const extractFiles = aPath =>
            lstatAsync(aPath).then(stat => {
                if (stat.isDirectory()) {
                    return readPath(aPath);
                } else if (aPath.includes(targetWord)) {
                    return aPath.split('.')[0];
                }
                return false;
            });
        return readPath(targetPath);
    }
    
    findPath('../modules', 'routes')
    .then(results => {
        // do things with the results - which is an array of files that contain the targetWord
    })
    .catch(err => console.error(err));
    

    Not much to it at all.