一个很简单的需求,获取某个目录下所有图片的文件路径并且把结果输出到一个文本文件上
遇到这种问题,简单啊,直接写个递归深度搜索然后再判断一下文件后缀名,符合条件的文件路径直接push进一个数组,最后遍历完把数组输出到文本文件上就行了:
const fs = require("fs");
const path = require("path");
const a = [];
function visitor(node) {
fs.readdir(node, (err, children) => {
if (err) return console.error(err);
children.forEach(child => {
let childNode = path.join(node, child);
if (fs.statSync(childNode).isDirectory()) {
visitor(childNode);
return;
}
if (/\.(png|jpe?g|gif|svg)$/.test(childNode)) {
a.push(childNode);
}
});
});
}
好了,现在我们已经把所有符合条件的路径都塞进a数组了,那么现在我们来输出a吧。
等等,真的确定可以直接输出a吗:
visitor('/**要查询目录的路径*/');
console.log(a)
在这里输出a的话,你会惊讶地发现。。。 a里面是空的
其实也没必要惊讶,稍微理解一下Node.js的人都知道,fs.readdir
函数是一个异步函数,也就是说,当执行visitor(...)
的时候,Nodejs不会等visitor
里面的代码执行完之后再触发console.log(a)
,而是直接触发console.log(a)
,然后当fs.readdir读取完一个目录的时候再执行回调里面的a.push
。所以你输出的数组是空的。
类似的,使用while
广度搜索,遇上异步也是完蛋:
const dirs = ["/**要搜索的目录文件路径*/"];
while (dirs.length > 0) {
let node = dirs.shift();
fs.readdir(node, (err, children) => {
/** ... */
if (fs.statSync(childNode).isDirectory()) {
dirs.push(childNode);
return;
}
/** .... */
});
/**执行到这里,GG,dirs.length为0*/
}
所以一般情况下,想都没想就直接使用fs.readdir
的同步方法fs.readdirSync
,然后就可以等待函数执行结束后自然就可以拿到a数组的内容。
但是这样就不是本文的讨论范围内了。。。
所以请大家先不要看下文,先想一想有什么办法能够让DFS(深度优先搜索)、BFS(广度优先搜索)这两个算法能够在Nodejs异步里面拿到最后的回调?
或许问题应该表述为,如何在访问树的每个节点的时候判断该节点是否为搜索的最后一个节点。
其实这个问题最后我也是想不明白的,想了一夜,毕竟个人能力有限。。。然后就把这个问题抛到群里面,没想到没一会儿就被一个长期潜水的dalao给解决了。(dalao牛逼!)
在这里我就作为dalao 的传声筒,将dalao想到的两个方法分享给大家:
第一个办法也很简单,只需要添加一个变量,每调用一次异步api就加一,当异步回调响应的时候就减一,然后判断一下当前这个变量是不是0,是的话,证明所有异步回调执行完毕。
当然前提是该异步api每调用一次只会有一次异步回调响应,如果有多次回调响应的,比如nodejs里面http
模块的request.on('data',callback)
它就可能会有多次响应,调用次数与响应次数并不一一对应,因此不适用这个变量计数的办法。
下面是代码:
const fs = require("fs");
const path = require("path");
let count = 0;
let a=[];
function visitor(node) {
count++;
fs.readdir(node, (err, children) => {
count--;
if (err) throw err;
let len=children.length
children.forEach((child,index) => {
let childNode=path.join(node,child);
if(fs.statSync(childNode).isDirectory()){
visitor(childNode);
return
}
if (/\.(png|jpe?g|gif|svg)$/.test(childNode)) {
a.push(childNode);
}
if(count===0&&index===len-1){
console.log(a)
}
});
});
}
visitor("/**要搜索的目录文件路径*/")
这是执行结果:
里面专门声明一个变量
endCount
来统计那个end
在查询中打印了多少次,最后结果是打印了一次,而且是在所有n
打印完之后再打印的,说明计数法可以作为异步DFS结束的判断。(当然这里是假设所有文件夹下面都有文件的,如果你拿这段代码去跑一个空文件夹的话,是会出问题的,因此还需要做一层判断,但这里就不展开了)
总之,
dalao牛逼!
第二个办法,是针对BFS的,dalao说很简单,不要使用while
去做,改用递归去写就可以了。
于是我试了一下:
const fs = require("fs");
const path = require("path");
const a = [path.join(__dirname, "node_modules")];
let n = 0;
let endCount = 0;
const visitor = nodes => {
if (nodes.length === 0) {
return;
}
const node = nodes.shift();
fs.readdir(node, (err, children) => {
children.forEach(child => {
let childNode = path.join(node, child);
if (fs.statSync(childNode).isDirectory()) {
a.push(childNode);
return;
}
n++;
console.log(n);
if (a.length === 0) {
endCount++;
console.log("end:", endCount);
}
});
visitor(a);
});
};
visitor(a);
结果:
dalao牛逼!
为毛换个递归去写这种想法不会出现跳入我脑中呢。。。我果然还是需要学习一个。。。
如果你有更好的方法,欢迎评论区贴出来大家一起交流呗。。。