Node.js使用fs异步API实现递归文件遍历返回空数组问题排查
异步写法问题原因及修正方案
核心错误原因
所有异步写法的问题根源都是Node.js异步API的非阻塞执行特性:fs.readdir 是异步IO接口,调用后会立刻返回继续执行后续代码,传入的回调函数要等操作系统完成目录读取操作后才会被事件循环调度执行。你写的return语句、打印结果的语句都执行在回调之前,自然拿不到回调里填充的数据。
另外两个版本都存在递归调用未处理子目录返回结果的问题,子目录的文件不会被合并到上层的结果数组中。
两个错误写法的具体问题
第一个版本
- 函数末尾的
return the_files执行在fs.readdir回调之前,此时数组还没有被填充,永远返回空数组。 - 递归调用
getTheFiles时没有接收返回的子目录文件列表,子目录的文件不会被添加到当前数组。
第二个版本
async_ver_getTheFiles没有定义返回值,所以result变量天然是undefined。filePacker的执行时机晚于外层console.log(result),所以先打印undefined,之后才会打印回调内的正确结果。- 同样没有处理递归调用的返回值,子目录文件不会被合并。
推荐修正方案:async/await + fs.promises
现在Node.js官方推荐使用Promise化的异步API搭配async/await语法,写法逻辑和同步版本几乎一致,没有回调嵌套的问题,代码可读性更高:
const fs = require('fs/promises'); const path = require('path'); async function getTheFiles(dir) { const the_files = []; let files = []; try { files = await fs.readdir(dir, { withFileTypes: true }); } catch (error) { console.error(`调用fs.readdir失败,传入的dir值为:'${dir}'\n`); console.error(error); return the_files; } for (const file of files) { // 修正你同步版本的bug:这里不能用return,否则遇到第一个点文件就会终止整个目录遍历 if (file.name[0] === '.') continue; const fullPath = path.resolve(dir, file.name); if (file.isFile()) { the_files.push(fullPath); } else if (file.isDirectory()) { // 等待子目录遍历完成,合并结果到当前数组 const subFiles = await getTheFiles(fullPath); the_files.push(...subFiles); } } return the_files; } // 调用方式:async函数返回Promise,需要用await或者then拿到结果 // 方法1:then调用 getTheFiles('/testdir').then(files => { console.log(files); }); // 方法2:在async作用域内用await调用 // (async () => { // const files = await getTheFiles('/testdir'); // console.log(files); // })();
输出结果和你正常运行的同步版本完全一致。
回调风格修正方案(不推荐)
如果你必须使用回调写法,需要手动计数处理异步并行的逻辑,确保所有子目录都遍历完成后再调用回调返回结果:
const fs = require('fs'); const path = require('path'); function getTheFiles(dir, callback) { const the_files = []; fs.readdir(dir, { withFileTypes: true }, (err, files) => { if (err) { console.log('Unable to scan directory: ' + err); return callback(err); } let processedCount = 0; const totalCount = files.length; if (totalCount === 0) return callback(null, the_files); files.forEach(file => { if (file.name[0] === '.') { processedCount++; if (processedCount === totalCount) callback(null, the_files); return; } const fullPath = path.resolve(dir, file.name); if (file.isFile()) { the_files.push(fullPath); processedCount++; if (processedCount === totalCount) callback(null, the_files); } else if (file.isDirectory()) { getTheFiles(fullPath, (err, subFiles) => { if (err) return callback(err); the_files.push(...subFiles); processedCount++; if (processedCount === totalCount) callback(null, the_files); }); } }); }); } // 调用方式 // getTheFiles('/testdir', (err, files) => { // if (err) return console.error(err); // console.log(files); // });
内容的提问来源于stack exchange,提问作者phoenixdown
相关产品推荐
相关产品推荐

