Node.js循环嵌套异步回调的填坑总结
举个栗子:
const {join, parse, resolve} = require('path')
const fs = require('fs')
/**
* 遍历目录下所有文件
* @param {String} filePath 要遍历的文件夹路径
* @param {String[]} fileArr 保存文件路径的数组
*/@returns 文件数组
const getFiles = async (filePath, fileArr = []) => {
// 定义递归调用时保存文件路径的数组
var tFile = fileArr
// 读取filePath下所有文件(只会读取文件名和目录名)
const files = fs.readdirSync(filePath)
// 遍历读取到的文件信息(其中可能包含目录)
files.forEach(file => {
// 根据filePath和文件名,组合成完整路径
const fileAbsPath = join(filePath, file)
// 判断该路径是目录还是文件
fs.stat(fileAbsPath, (err, stats) => {
if (!err) {
// 如果是文件
if (stats.isFile()) {
// 加入文件数组
tFile.push(fileAbsPath)
} else {
// 否则,带着文件数组递归
getFiles(fileAbsPath, tFile)
}
}
})
})
return tFile
}
上面的代码看似没有半点毛病,当我们调用的时候,返回的却是这样的东西:
const models = await getFiles(appPath)
console.log(models)
#输出结果:
PS G:\projects\node\egg-core> npm run start
> egg-core@1.0.0 start G:\projects\node\egg-core
> node app.js
[]
...
...
欸我就奇了怪了,明明逻辑没有问题啊,怎么返回一个空数组嘞
仔细检查源码后发现,fs.stat这个函数是一个异步回调函数,在nodejs中,异步IO会被放入事件栈,然后优先执行后面的代码
解决方法:
fs模块中每个异步的方法都有一个相对应的同步方法,使用同步方法替代异步方法即可
/**
* 遍历目录下所有文件
* @param {*} filePath 要遍历的文件夹路径
* @param {*} fileArr 保存文件路径的数组
* @returns 文件数组
*/
const getFiles = async (filePath, fileArr = []) => {
const tFile = fileArr
const files = fs.readdirSync(filePath)
files.forEach(file => {
const fileAbsPath = join(filePath, file)
// 使用fs.statSync替代fs.stat异步回调
const isFile = fs.statSync(fileAbsPath).isFile()
if (isFile) tFile.push(fileAbsPath)
else getFiles(fileAbsPath, tFile)
})
return tFile
}
const getFileStatSync = filePath => {
return new Promise((resolve, reject) => {
fs.stat(filePath, (err, stats) => {
if (!err) {
resolve(stats)
return
}
reject(err)
})
})
}
然后在需要调用的地方添加await
/**
* 遍历目录下所有文件
* @param {*} filePath 要遍历的文件夹路径
* @param {*} fileArr 保存文件路径的数组
* @returns 文件数组
*/
const getFiles = async (filePath, fileArr = []) => {
const tFile = fileArr
const files = fs.readdirSync(filePath)
files.forEach(file => {
const fileAbsPath = join(filePath, file)
// 此处使用await关键词,要在函数定义的地方添加async关键字,如本函数['async (filePath, fileArr = [])']
const isFile = await getFileStatSync(fileAbsPath).isFile()
if (isFile) tFile.push(fileAbsPath)
else getFiles(fileAbsPath, tFile)
})
return tFile
}