【问题标题】:JS build object recursivelyJS递归构建对象
【发布时间】:2017-01-02 06:31:40
【问题描述】:

我正在尝试使用 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);

编辑:: 我现在确实有一些工作,虽然它不像我想要的那么优雅。下面是我所做的。如果有人对优化有任何建议,我会很高兴听到它!

最新(工作)代码:

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"
            }
        }
    }
}

【问题讨论】:

  • JavaScript 是异步的。 meindexer() 完成之前不会等于任何东西,但console.log(me) 会立即执行。
  • 查看承诺。
  • 你能提供一个示例目录结构和预期的输出吗?

标签: javascript node.js recursion indexing fs


【解决方案1】:

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

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

例如,考虑一个文件是否在您的结构中命名为 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 的任何内容纠缠在一起。解析目录和构建树是它自己的事情,如果您需要一个对象来继承该行为,还有其他方法可以做到。

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

$ 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" }

【讨论】:

    【解决方案2】:

    结果只能异步获得,因此您试图过早地输出结果。内部代码只是稍后执行。

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

    由于您有一个递归调用,因此您也必须使用 Promise 来解决它。

    注意:请注意,您在与“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);
    });
    

    我为您的外部引用制作了一些虚拟函数来使这个 sn-p 工作:

    // 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);
    });

    【讨论】:

    • 嗨,trincot,这看起来很棒!不幸的是,它实际上不起作用。它与我之前的代码有类似的问题,即它从不返回任何内容。我在想它可能会陷入递归循环或其他什么?我尝试删除错误时的拒绝(),因为我仍然希望在该特定位置发生错误的情况下进行回调......不过,您对问题的描述绝对准确,这是由于代码的异步性质。我将添加我在上面工作的最新代码。虽然它没有我想要的那么优雅,所以也许你会有更多的想法?
    • 你调试代码了吗?在不同的地方放一些console.log 语句,看看它的作用。由于我没有您的Self 对象、identify 函数等,我无法测试此代码。
    • 是的,先生,我已经在几个地方添加了控制台日志,而且里面的一切似乎都正常工作了。 (在函数中记录结果可以正常工作)但永远不会触发回调。 :(
    • resolve 被调用时,最终的回调应该被触发。你确定它到达那里?我确实看到我需要为递归调用 ident.children = indexer(descendant); 做点什么。你能把它放在 cmets 中直到你发现问题吗?这将使调试更容易
    • 当然,除了,我不确定你想要在 cmets 中得到什么?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-18
    • 1970-01-01
    • 1970-01-01
    • 2020-09-04
    • 2012-12-14
    • 2021-11-09
    相关资源
    最近更新 更多