Question

I am trying to return the file in a directory with the earliest modification date. This approach seems to be failing in the createFileDateMap function. I want to reduce over an array of file paths and create an object with the filename and mod-dates. The getModDate function is an asynchronous fs.lstat call. I can't seem to set acc of my reducer to the values from within a .then() block. I'm not sure how to achieve reducing when the values depend on asynchronous call

var _ = require('lodash'),
    fs = require('fs'),
    path = require('path'),
    Q = require('q');

function checkDir(dir) {
    // Check if given path is valid directory
    var deferred = Q.defer();
    fs.lstat(dir, deferred.makeNodeResolver());
    return deferred.promise.invoke('isDirectory');
}

function getFiles(dir) {
    // Get all files within a directory
    var deferred = Q.defer();
    fs.readdir(dir, deferred.makeNodeResolver());
    return deferred.promise;
}

function makeFullPathFileArr(dir, files) {
    // Return array of full paths
    return _.map(files, function(file) {
        return path.join(dir, file);
    });
}

function getModDate(file) {
    // Return modification date of file
    var deferred = Q.defer();
    fs.stat(file, deferred.makeNodeResolver());
    return deferred.promise.get('mtime');
}

function createFileDateMap(filesArr) {
    // Return an obj of file paths and modification dates as Date objects
    // {{ file1: Date, file2: Date }}
    var fileDateMap = _.reduce(filesArr, function(acc, file) {
        getModDate(file)
            .then(function(modDate) {
                acc[file] = moment(modDate);
            });
        return acc;
    }, {});
    return fileDateMap;
}

function getMinDateFile(mapObj) {
    // return the file name which has the earliest modification date
    var dates = _.transform(mapObj, function(result, date, key) {
        result[key] = new Date(date);
    });
    var minDate = new Date(Math.min.apply(null, _.values(dates)));
    var invertedMapObj = _.invert(mapObj);

    return invertedMapObj[minDate];
}

var dir = '../reports';
checkDir(dir)
    .then(function(exist) {
        if(exist) {
            getFiles(dir)
                .then(function(fileNames) {
                    return makeFullPathFileArr(dir, fileNames);
                })
                .then(function(fullpathsArr) {
                    return createFileDateMap(fullpathsArr);
                })
                .then(function(fileAndDatesObj) {
                    console.log(getMinDateFile(fileAndDatesObj));
                });
        }
    })
    .catch(function(err) {
        console.log(err);
    });
Was it helpful?

Solution

Thanks to @BenjaminGruenbaum for the help. :)

function createFileDateMap(filesArr) {
    // Return an obj of file paths and modification dates as Date objects
    // {{ file1: Date, file2: Date

    return Q.all(_.map(filesArr, getModDate))
        .then(function(modDates) {
           return _.zipObject(filesArr, modDates);
        });
}

OTHER TIPS

The problem is that reduce is synchronous and does not know about the promises you use in its callback. You're returning the fileDateMap, which is an empty object. There are two solutions to this problem:

  • Invoke all the getModDates in parallel, use Q.all to get a promise for the array of dates, and reduce over that in a then callback.

    function createFileDateMap(filesArr) {
        // Return an obj of file paths and modification dates as Date objects
        // {{ file1: Date, file2: Date }}
        return Q.all(_.map(filesArr, function(file) {
            return getModDate(file).then(function(date) {
                return {name:file, date:date};
            });
        })).then(function(fileAndDateArr) {
            return _.reduce(fileAndDateArr, function(acc, o) {
                acc[o.file] = moment(o.date);
                return acc;
            }, {});
        });
    }
    
  • Make the accumlutor a promise for the map object, and reduce "asynchronously" over the array by chaining lots of then calls.

    function createFileDateMap(filesArr) {
        // Return an obj of file paths and modification dates as Date objects
        // {{ file1: Date, file2: Date }}
        return _.reduce(filesArr, function(acc, file) {
            return acc.then(function(fileDateMap) {
                return getModDate(file).then(function(modDate) {
                    fileDateMap[file] = moment(modDate);
                    return fileDateMap;
                });
            });
        }, Q({}));
    }
    
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top