JS递归地构建对象

时间:2016-08-25 17:53:42

标签: javascript node.js recursion indexing fs

我正在尝试使用nodeJS构建文件结构索引。我正在使用fs.readir函数来迭代文件,这很好。我的问题是下降到目录结构并返回具有正确结构的完整对象。

我有一个名为identify的简单函数,当给定文件名“myfile.txt”时会返回一个对象{name:“myfile”,类型:“txt”},这将解释下面这部分函数。 。

我的问题是当我将索引器运行到“me”变量时没有返回任何内容。但是,console.log(results)行确实返回。这让我很困惑。

任何帮助将不胜感激!

indexer = 
    function(directory){     
        Self.indexleft++;
        var results = {};
        Self.client.readdir(directory, function(err,fileLst){
            if(err){ return; }
            for(var count=0; count < fileLst.length; count++){
                var ident = identify(fileLst[count]);
                if(ident.type = 'dir'){
                    var descendant = (directory !== '') ? 
                        directory + '\\' + ident.name : ident.name;
                    ident.children = indexer(descendant);
                                }
                    //directory = (directory.split('\\').pop());
                    results[ident.name] = ident;
                 }
                 console.log(results);
                 return results;
             });
         }
         var me = indexer(''); console.log(me);

EDIT :: 我实际上已经有了一些工作,虽然它并不像我想的那么优雅。以下是我的所作所为。如果有人对优化有任何建议,我很乐意听到它!

最新(工作)代码:

var events = require('events'),
    event = new events.EventEmitter(),
setToValue = function(obj, value, path) {
    path = path.split('\\');
    for (i = 0; i < path.length - 1; i++)
        obj = obj[path[i]];
    obj[path[i]] = value;
},
identify = function(file){
    var split = file.split('.'),
        type = (split.length > 1) ? split.pop() : 'dir',
        filename = split.join('.');
    return { name: filename, type: type };
};
Indexer = function(cli,dir,callback){  
    this.client = cli; // File reading client
    this.startDir = dir; // Starting directory
    this.results = {}; // Result object
    this.running = 0; // How many itterations of start() are running
    this.start(dir); // Start indexing
    this.monit(); // Start never returns anything, monit() checks ever 5 seconds and will fire callback if 0 itterations are running.
    this.callbackDone = false; // Checks whether the callback has already been fired. Important in case of interval staggering
    this.cb = callback;
}
Indexer.prototype = {
    start: function(directory){        
        var Self = this;
        Self.running++;
        Self.client.readdir(directory, function(err,fileLst){
            if(err){ Self.running--; return; }
            for(var count=0; count < fileLst.length; count++){
                var ident = identify(fileLst[count]);
                var descendant = (directory !== '') ? directory + '\\' + ident.name : ident.name;
                if(ident.type === 'dir'){                
                    Self.start(descendant);
                }
                setToValue(Self.results, ident, descendant);
            }
            Self.running--;
            console.log('running' + Self.running);
        });
    },
    monit: function(){
        var Self = this;
        Self.intervalA = setInterval(function(){
            if(Self.running < 1){                
                if(!Self.callbackDone){ 
                    this.callbackDone=true; 
                    Self.cb(Self.results);
                }
                clearInterval(Self.intervalA);

            }
        }, 5000)
    }
}

var ix = new Indexer(Self.client,'',function(res){
                        console.log("Index Complete!");
                        fs.writeFile(path.join(Self.localLibBase,'/index.json'), JSON.stringify(res), (err)=> {
                            console.log("FileWrite Complete!");
                        });
                    });

返回的对象结构示例:

{
    "Applications" : {
        "name" : "Applications",
        "type" : "dir",
        "Microsoft Exchange Server 2007" : {
            "name" : "Microsoft Exchange Server 2007",
            "type" : "dir",
            "Microsoft Exchange Server 2007 SP1" : {
                "name" : "Microsoft Exchange Server 2007 SP1",
                "type" : "iso"
            }
        }
    }
}

2 个答案:

答案 0 :(得分:2)

结果只能异步提供,因此您尝试过早输出结果。内部代码仅在稍后执行。

您可以通过多种方式解决此问题。使用异常代码的一个非常好的解决方案是使用promises

当你有递归电话时,你也必须用承诺解决这个问题。

注意:注意你在与“dir”的比较中有一个错误:你分配而不是比较。

以下是您的代码的外观:

var indexer = function(directory) {
    // return a promise object
    return new Promise(function (resolve, reject) {
        Self.indexleft++;
        var results = {};
        Self.client.readdir(directory, function(err,fileLst){
            if(err) { 
                reject(); // promise is rejected
                return;
            }
            // "Iterate" over file list asyonchronously
            (function nextFile(fileList) {
                if (!fileList.length) {
                    resolve(results);  // promise is resolved
                    return;
                }
                var file = fileLst.shift(); // shop off first file
                var ident = identify(file); 
                results[ident.name] = ident;
                if(ident.type === 'dir'){ // There was a bug here: equal sign!
                    var descendant = directory !== '' 
                            ? directory + '\\' + ident.name : ident.name;
                    // recursively call indexer: it is again a promise!        
                    indexer(descendant).then(function (result) {
                        ident.children = result;
                        // recursively continue with next file from list
                        nextFile(fileList);
                    });
                } else {
                    nextFile(fileLst);
                }
            })(fileLst); // start first iteration with full list
        });
    });
};

// Call as a promise. Result is passed async to callback. 
indexer('').then(function(me) {
    console.log(me);
});

我为您的外部引用创建了一些虚函数,以使这个代码片段起作用:

// Below code added to mimic the external references -- can be ignored
var filesystem = [
    "",
    "images",
    "images\\photo.png",
    "images\\backup",
    "images\\backup\\old_photo.png",
    "images\\backup\\removed_pic.jpg",
    "images\\panorama.jpg",
    "docs",
    "docs\\essay.doc",
    "readme.txt",
];

var Self = {
    indexLeft: 0,
    client: {
        readdir: function (directory, callback) {
            var list = filesystem.filter( path => 
                    path.indexOf(directory) == 0 
                    && path.split('\\').length == directory.split('\\').length + (directory!=='')
                    && path !== directory
            ).map ( path => path.split('\\').pop() );
            setTimeout(callback.bind(null, 0, list), 100);
        }
    }
}

function identify(item) {
    return {
        name: item,
        type: item.indexOf('.') > -1 ? 'file' : 'dir'
    };
}
// Above code added to mimic the external references -- can be ignored

var indexer = function(directory) {
    // return a promise object
    return new Promise(function (resolve, reject) {
        Self.indexleft++;
        var results = {};
        Self.client.readdir(directory, function(err,fileLst){
            if(err) { 
                reject(); // promise is rejected
                return;
            }
            // "Iterate" over file list asyonchronously
            (function nextFile(fileList) {
                if (!fileList.length) {
                    resolve(results);  // promise is resolved
                    return;
                }
                var file = fileLst.shift(); // shop off first file
                var ident = identify(file); 
                results[ident.name] = ident;
                if(ident.type === 'dir'){ // There was a bug here: equal sign!
                    var descendant = directory !== '' 
                            ? directory + '\\' + ident.name : ident.name;
                    // recursively call indexer: it is again a promise!        
                    indexer(descendant).then(function (result) {
                        ident.children = result;
                        // recursively continue with next file from list
                        nextFile(fileList);
                    });
                } else {
                    nextFile(fileLst);
                }
            })(fileLst); // start first iteration with full list
        });
    });
};

// Call as a promise. Result is passed async to callback. 
indexer('').then(function(me) {
    console.log(me);
});

答案 1 :(得分:0)

你从你拥有的代码中期望返回的对象并不是很明显,但我仍然可以帮助你获得这个对象。

对象的形状很糟糕,因为你使用文件名作为对象的键,但这是错误的。密钥应该是程序已知的标识符,因为文件名几乎可以是任何东西,使用文件名作为密钥是非常糟糕的。

例如,考虑文件是否在您的结构中命名为name

{ "Applications" : {
    "name" : "Applications",
    "type" : "dir",
    "name" : {
      "name" : "name"
       ... } } }
是的,它刚破了。别担心,我们的解决方案不会遇到这样的麻烦。

const co = require('co')
const {stat,readdir} = require('fs')
const {extname,join} = require('path')

// "promisified" fs functions
const readdirp = path =>
  new Promise ((t,f) => readdir (path, (err, res) => err ? f (err) : t (res)))

const statp = fd =>
  new Promise ((t,f) => stat (fd, (err,stats) => err ? f (err) : t (stats)))

// tree data constructors
const Dir = (path, children) =>
  ({type: 'd', path, children})

const File = (path, ext) =>
  ({type: 'f', path, ext})

// your function
const indexer = function* (path) {
  const stats = yield statp (path)
  if (stats.isDirectory ())
    return Dir (path, yield (yield readdirp (path)) .map (p => indexer (join (path,p))))
  else
    return File (path, extname (path))
}

这是一个很好的设计,因为我们没有用任何Self.client纠结目录树构建。解析目录和构建树是它自己的事情,如果你需要一个Object来继承这种行为,还有其他方法可以做到。

好的,让我们设置一个示例文件树,然后运行它

$ mkdir test
$ cd test
$ mkdir foo
$ touch foo/disk.iso foo/image.jpg foo/readme.txt
$ mkdir foo/bar
$ touch foo/bar/build foo/bar/code.js foo/bar/migrate.sql

使用indexer很容易

// co returns a Promise
// once indexer is done, you will have a fully built tree
co (indexer ('./test')) .then (
  tree => console.log (JSON.stringify (tree, null, '  ')),
  err  => console.error (err.message)
)

输出(为简洁而删除了一些\n

{
  "type": "d",
  "path": "./foo",
  "children": [
    {
      "type": "d",
      "path": "foo/bar",
      "children": [
        { "type": "f", "path": "foo/bar/build", "ext": "" },
        { "type": "f", "path": "foo/bar/code.js", "ext": ".js" },
        { "type": "f", "path": "foo/bar/migrate.sql", "ext": ".sql" }
      ]
    },
    { "type": "f", "path": "foo/disk.iso", "ext": ".iso" },
    { "type": "f", "path": "foo/image.jpg", "ext": ".jpg" },
    { "type": "f", "path": "foo/readme.txt", "ext": ".txt" }
  ]
}

如果您在文件路径上尝试indexer,则不会失败

co (indexer ('./test/foo/disk.iso')) .then (
  tree => console.log (JSON.stringify (tree, null, '  ')),
  err  => console.error (err.message)
)

输出

{ "type": "f", "path": "./foo/disk.iso", "ext": ".iso" }