2017-02-23 55 views
2

对于我正在编写的Node JS模块,我想使用异步函数Stats.isFile()作为Array.filter()函数的回调函数。下面我有一个我想要实现的例子,但使用snychronous等价物。我无法弄清楚如何包装异步功能,以便我可以在Array.filter()函数中使用。如何使用异步函数作为Array.filter()的比较函数?

const fs = require('fs') 

exports.randomFile = function(dir = '.') { 
    fs.readdir(dir, (err, fileSet) => { 
     const files = fileSet.filter(isFile) 
     const rnd = Math.floor(Math.random() * files.length); 
     return files[rnd]) 
    }) 
} 

function isFile(item) { 
    return (fs.statSync(item)).isFile() 
} 

回答

1

不能使用异步回调与.filter().filter()需要同步结果,并且无法从异步操作中获取同步结果。所以,如果你打算使用异步fs.stat(),那么你必须使整个操作异步。

下面介绍一种方法。请注意,即使randomFile()需要异步传回其结果。我们在这里使用回调。

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

exports.randomFile = function(dir, callback) { 
    fs.readdir(dir, (err, files) => { 
     if (err) return callback(err); 

     function checkRandom() { 
      if (!files.length) { 
       // callback with an empty string to indicate there are no files 
       return callback(null, ""); 
      } 
      const randomIndex = Math.floor(Math.random() * files.length); 
      const file = files[randomIndex]; 
      fs.stat(path.join(dir, file), (err, stats) => { 
       if (err) return callback(err); 
       if (stats.isFile()) { 
        return callback(null, file); 
       } 
       // remove this file from the array 
       files.splice(randomIndex, 1); 
       // try another random one 
       checkRandom(); 
      }); 
     } 

     checkRandom(); 
    }); 
} 

而且,下面介绍如何使用另一个模块的异步接口。

// usage from another module: 
var rf = require('./randomFile'); 
fs.randomFile('/temp/myapp', function(err, filename) { 
    if (err) { 
     console.log(err); 
    } else if (!filename) { 
     console.log("no files in /temp/myapp"); 
    } else { 
     console.log("random filename is " + filename); 
    } 
}); 
+0

感谢这个脚本,它看起来像是'生产就绪'! :-)可能需要添加一个'path.join(dir,file)',以便'fs.stat'工作。 – jfix

+0

@jfix - 好点,我补充说。 – jfriend00

+1

@jfix - 当需要第二个参数(回调)时,您无法在第一个参数上有用地使用ES6默认值。你可以这样声明,但触发默认值的唯一方法是将“undefined”作为第一个参数传递,这很难讲出来。所以,我不认为'dir'的ES6默认参数在这种情况下是有意义的。它可以以旧式的方式实现,代码检查是否传递了一个或两个参数,如果只传递一个参数,则分配默认的'dir'。但是,我不知道如何让ES6为你做到这一点。 – jfriend00

1

如果你去异步,你必须从入口点去异步,所以randomFile需要一些方法来“回报”异步值(通常通过回调,承诺,或流)。

我不知道你的文件结构是怎么样的,而不是检查作为文件的所有条目,我会选择一个随机条目,检查它是否是文件,如果不是再试一次。

这可能看起来像这样

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

exports.randomFile = (dir = '.', cb) => { 
    fs.readdir(dir, (err, files) => { 
     if (err) { return cb(err); } 
     pickRandom(files.map((f) => path.join(dir, f)), cb); 
    }); 
} 

function pickRandom (files, cb) { 
    const rnd = Math.floor(Math.random() * files.length); 
    const file = files[rnd]; 
    fs.stat(file, (err, stats) => { 
     if (err) { 
      return cb(err); 
     } 
     if (stats.isFile()) { 
      return cb(null, file); 
     } else { 
      return pickRandom(files, cb); 
     } 
    }) 
} 
+0

在再次调用'pickRandom()'之前,可能应该从'files'数组中删除非文件,以便更清楚地找到实际文件。如果没有文件(只有目录条目)可以无限循环,您还必须做好准备。 – jfriend00

+0

@squiddle这真棒,谢谢!虽然它很好地解决了这个问题,但出于好奇,如何使用Array.filter()的异步函数呢? :-)因为在下一步中,我打算通过文件扩展名来添加过滤,例如。 – jfix

+0

@ jfriend00没问题,在生产实现中,我会在X尝试完成后尝试限制计数器以使pickRandom中止。文件可能是空的,它也会失败。 – squiddle