- 教程
- CLI
【免费下载链接】learnyounode
Learn You The Node.js For Much Win! An intro to Node.js via a set of self-guided workshops.
learnyounode 的Filtered LS(filtered_ls)是入门 Node.js 文件系统异步编程的经典实战练习。本练习要求你编写一个接收「目录路径」与「文件扩展名」两个命令行参数的小程序,只输出目录中扩展名匹配的文件,且必须使用异步 I/O。读完本文,你将掌握process.argv的参数解析、fs.readdir异步回调、path.extname的扩展名判断,并能看懂 learnyounode 自带的自动化校验原理,写出可通过verify的正确解法。
练习目标与任务拆解
filtered_ls 的完整任务描述位于 exercises/filtered_ls/problem.ru.md(俄文版)与 exercises/filtered_ls/problem.md(英文版),核心要求可以拆解为四条:
- 输入两个命令行参数:第一个参数是目录路径(形如
/path/to/dir/),第二个参数是需要过滤的扩展名(例如txt)。 - 过滤规则:只输出目录中以
.+ 该扩展名结尾的文件。例如传入txt,就只列出.txt文件。注意第二个参数不会自带点号前缀,需要自行拼接'.' + ext。 - 逐行输出:每个匹配文件单独占一行,输出到控制台。
- 强制异步:必须使用异步 I/O(如
fs.readdir),使用fs.readdirSync之类的同步方法会导致校验不通过。
英文原版中还额外强调了一个初学者常踩的坑:process.argv数组的前两个值被 Node 保留用于系统信息(process.argv[0]是 Node 可执行文件路径,process.argv[1]是脚本路径),因此你自定义的第一个参数要从process.argv[2]开始取——中文描述版未单独列出,但这是所有版本通用的约束。
核心前置知识
1. 用process.argv读取参数
Node 把启动脚本时传入的所有参数放进process.argv。由于前两项被系统占用,练习所需的目录和扩展名分别落在process.argv[2]和process.argv[3]:
const folder = process.argv[2] // 目录路径 const ext = '.' + process.argv[3] // 扩展名,手动补上点号2.fs.readdir:异步读取目录列表
练习提示明确给出了fs.readdir()的用法:第一个参数是路径名,第二个参数是回调函数,回调签名固定为:
function callback (err, list) { /* ... */ }其中list是目录下所有文件名的字符串数组。异步风格要求把后续处理全部写进回调,错误通过回调的第一个参数err暴露。fs模块的完整文档在 docs-nodejs/fs.html,其中fs.readdir一节对应本练习。
3.path.extname:提取文件扩展名
判断文件名是否匹配扩展名,最稳妥的方式不是字符串endsWith,而是用path模块的extname方法——它返回文件名(含点号)的扩展名部分,例如path.extname('learnyounode.txt')返回'.txt',与补好点号的过滤条件可以直接全等比较。path模块文档见 docs-nodejs/path.html。
参考解法:官方solution.js全量实现
仓库在 exercises/filtered_ls/solution/solution.js 给出了官方参考实现,正是练习提示所引导思路的直接落地:
'use strict' const fs = require('fs') const path = require('path') const folder = process.argv[2] const ext = '.' + process.argv[3] fs.readdir(folder, function (err, files) { if (err) return console.error(err) files.forEach(function (file) { if (path.extname(file) === ext) { console.log(file) } }) })逐行解读:
ext = '.' + process.argv[3]在比较前为扩展名补点,与path.extname的返回格式对齐;- 回调第一行先做错误处理,出错时把错误打到
console.error并提前返回,避免用未定义的files继续执行; files.forEach遍历目录列表,path.extname(file) === ext命中即console.log(file)逐行输出;- 全程只有
fs.readdir这一个文件系统调用,且是异步形式,天然满足「必须异步 I/O」的硬性要求。
运行方式(本仓库定位为教学引导工具,实际练习时把下面的filtered-ls.js换成你自己的文件):
# 假设要列出当前目录下所有 .md 文件 node filtered-ls.js . md另一种常见写法:filter+forEach链式过滤
官方测试样例 test/filtered_ls/valid_01.js 提供了等价的链式写法,同样通过校验,可作为风格参考:
const fs = require('fs') const path = require('path') fs.readdir(process.argv[2], function (err, list) { if (err) { console.log(err) } list.filter(function (file) { return path.extname(file) === '.' + process.argv[3] }).forEach(function (file) { console.log(file) }) })两种写法实质相同:先用Array.prototype.filter把不匹配的文件剔除,再对结果逐行打印。选哪种取决于个人风格,功能与校验结果一致。
校验机制源码解析:异步与同步如何被检测
学习本练习时最值得深挖的,是 learnyounode 到底如何判定「用了异步而没用同步」。答案藏在三处源码里。
1. 测试目录与测试数据
exercises/filtered_ls/exercise.js 的 setup 阶段会在系统临时目录创建_learnyounode_<pid>测试目录,并把 exercises/filtered_ls/file-list.json 中列出的 15 个文件写入其中——注意列表中既有learnyounode.txt、w00t.txt这样的.txt文件,也有api.html、README.md,以及md、dat、wrrrrongdat这种没有扩展名或扩展名不完整的文件,专门用来检验你的过滤逻辑是否准确:
[ "learnyounode.dat", "learnyounode.txt", "learnyounode.sql", "api.html", "README.md", "CHANGELOG.md", "LICENCE.md", "md", "data.json", "data.dat", "words.dat", "w00t.dat", "w00t.txt", "wrrrrongdat", "dat" ]同时 setup 通过submissionArgs.unshift('md')和submissionArgs.unshift(testDir)为你的程序注入两个参数:目录路径和'md'扩展名。
2. 运行时包装:记录每一次fs调用
exercises/my_first_io/wrap.js 会以 phantom wrapper 的形式注入子进程,把fs、fs.promises(fsp)和util的所有方法包一层代理:每次调用都通过ctx.$captureStack捕获调用栈,若栈顶调用方正是参赛主程序,就把方法名记入ctx.fsCalls[m]。这样,你的程序**实际调用了哪些fs方法(无论同步还是异步)**会被完整记录。
3. verify 判定逻辑:同步即失败
exercise.js的 verify 处理器遍历exercise.wrapData.fsCalls中的方法名:
- 方法名以
Sync结尾(如readdirSync)→ 触发fail,提示使用了同步方法; - 否则(异步方法)→ 触发
pass; - 如果既没有同步也没有异步调用 → 触发
fail,提示完全没用到fs(对应 test/filtered_ls/invalid_02.js 的「未使用 fs」场景); - 最终结论是
usedAsync && !usedSync:用了异步且没碰同步才算通过。
这也解释了为什么 test/filtered_ls/invalid_01.js(用fs.readdirSync的版本)必然被判定为失败——它的方法名命中/Sync$/正则。
常见错误与调试清单
| 错误类型 | 表现 | 原因与对策 |
|---|---|---|
用readdirSync | verify 报fail.sync | 同步方法名以Sync结尾,被校验器直接识别;改为fs.readdir(folder, callback) |
| 过滤条件漏掉点号 | 输出为空或匹配错误 | 第二个参数不带.,需先'.' + ext再与path.extname(file)全等比较 |
用字符串拼接/endsWith判断 | 可能误伤wrrrrongdat、dat等无扩展名文件 | 扩展名比较应基于path.extname的规范返回 |
忘记err处理 | 目录不存在时崩溃或打印异常 | 回调首行if (err) return console.error(err) |
完全没调用fs | verify 报fail.unused | 练习要求必须使用异步 I/O,空实现无法通过 |
验证命令
练习自带校验器,编写完filtered-ls.js后执行:
$ learnyounode verify filtered-ls.jsverify会触发上文的完整链路:构建测试目录 → 注入两个参数 → 包装fs调用 → 比对结果与调用记录。若你的程序输出与预期文件列表一致、且只使用了异步方法,即判定通过。
小结
filtered_ls 表面上是「列文件 + 过滤扩展名」,实际上是一次对 Node.js 异步 I/O 心智模型的完整考验:从process.argv的参数定位,到fs.readdir的回调式错误处理,再到path.extname的规范化扩展名比较。通过阅读 solution.js、exercise.js 与 wrap.js,你不仅能写出正确解法,还能理解 learnyounode「用包装器记录fs调用、以方法名正则判定同步/异步」的自动化校验机制——这为后续my_first_io、make_it_modular等更复杂的练习打下了扎实基础。
- 教程
- CLI
【免费下载链接】learnyounode
Learn You The Node.js For Much Win! An intro to Node.js via a set of self-guided workshops.
相关推荐
learnyounode 实战:使用 fs.readdir 与 path.extname 实现按扩展名过滤的异步目录列表(filtered_ls 练习详解)
learnyounode 实战:使用 fs.readdir 与 path.extname 实现按扩展名过滤的异步目录列表(filtered_ls 练习详解) 本
教程CLIlearnyounode 实战:用 `fs.readdir()` + `path.extname()` 实现按扩展名过滤的目录文件列表(filtered_ls 练习详解)
learnyounode 实战:用 fs.readdir + path.extname 实现按扩展名过滤的目录文件列表(filtered_ls 练习详解) 这篇
教程CLIlearnyounode 过滤目录文件列表实战:掌握 fs.readdir 与 path.extname 的异步文件过滤
learnyounode 过滤目录文件列表实战:掌握 fs.readdir 与 path.extname 的异步文件过滤 导读 本篇技术指南围绕 learnyo
教程CLI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考