Express中使用fs.readdir无法更新外部变量的问题求助
Express中使用fs.readdir无法更新外部变量的问题求助
兄弟,你遇到的这个问题其实是Node.js里异步代码执行顺序的经典坑!我来给你掰扯清楚:
问题根源
fs.readdir是一个异步非阻塞的API,它不会等文件读取完成再往下走代码。你的代码执行顺序其实是这样的:
- 定义
CollectionsHtml和mdFiles - 调用
fs.readdir,但它不会立刻执行里面的回调函数,而是先把这个任务丢到事件队列里 - 直接往下跑后面的
for循环(这时候mdFiles还是空数组,所以循环根本不会执行) - 执行
CollectionsHtml.concat(...)、console.log(mdFiles)这些代码 - 最后才轮到
fs.readdir的回调函数执行,这时候才会往mdFiles里push内容,但这时候前面的代码都已经跑完了,自然拿不到更新后的值
另外还有个小细节:concat方法不会修改原字符串,它会返回一个新字符串!你之前用CollectionsHtml.concat(...)其实没起作用,应该用+=来修改原变量。
解决方案(给你三个常用的路子)
方案1:把所有依赖异步结果的代码丢到回调里
最简单的改法,就是把拼接HTML、发送响应的逻辑都放到fs.readdir的回调函数里面,这样就能保证等文件读取完成后再处理:
app.get('/collections', (req, res) => { let CollectionsHtml = ` <!DOCTYPE html> <head> <link href="style.css" rel="stylesheet" type="text/css"> </head> <body> <div id="container"> <div id="header"> <h1>Collections</h1> </div> <div id="sidebar"> <a href="/">Home</a> </div> <div id="collections">`; let mdFiles = []; fs.readdir(markdownDir, (err, files) => { if (err) { // 别忘了加错误处理!比如返回500页面 res.status(500).send('读取文件失败'); return; } files.forEach(file => { const informalName = file.split(".")[0]; mdFiles.push(informalName); console.log(`loaded ${file} in /collections`); // 这里直接拼接HTML片段 CollectionsHtml += `<div id="article"> <a href="/collections/${informalName}">${informalName}</a> </div>`; }); // 拼接HTML结尾 CollectionsHtml += `</div > </div > </body > <script type="text/javascript src=""></script> `; console.log(mdFiles.join("")); console.log(CollectionsHtml); res.send(CollectionsHtml); }); });
方案2:用同步版本的fs.readdirSync
如果你的场景对性能要求不高(比如读取的文件不多),可以用同步阻塞的fs.readdirSync,这样代码顺序就和你预期的一样了:
app.get('/collections', (req, res) => { let CollectionsHtml = ` <!DOCTYPE html> <head> <link href="style.css" rel="stylesheet" type="text/css"> </head> <body> <div id="container"> <div id="header"> <h1>Collections</h1> </div> <div id="sidebar"> <a href="/">Home</a> </div> <div id="collections">`; let mdFiles = []; try { // 同步读取,会等读取完成再往下走 const files = fs.readdirSync(markdownDir); files.forEach(file => { const informalName = file.split(".")[0]; mdFiles.push(informalName); console.log(`loaded ${file} in /collections`); CollectionsHtml += `<div id="article"> <a href="/collections/${informalName}">${informalName}</a> </div>`; }); } catch (err) { res.status(500).send('读取文件失败'); return; } CollectionsHtml += `</div > </div > </body > <script type="text/javascript src=""></script> `; console.log(mdFiles.join("")); console.log(CollectionsHtml); res.send(CollectionsHtml); });
方案3:用Promise+async/await(更现代的写法)
Node.js的fs模块从v10开始有Promise版本,用async/await写起来更清爽,不会有回调嵌套:
// 先引入promise版的fs const fs = require('fs').promises; app.get('/collections', async (req, res) => { let CollectionsHtml = ` <!DOCTYPE html> <head> <link href="style.css" rel="stylesheet" type="text/css"> </head> <body> <div id="container"> <div id="header"> <h1>Collections</h1> </div> <div id="sidebar"> <a href="/">Home</a> </div> <div id="collections">`; let mdFiles = []; try { const files = await fs.readdir(markdownDir); files.forEach(file => { const informalName = file.split(".")[0]; mdFiles.push(informalName); console.log(`loaded ${file} in /collections`); CollectionsHtml += `<div id="article"> <a href="/collections/${informalName}">${informalName}</a> </div>`; }); } catch (err) { res.status(500).send('读取文件失败'); return; } CollectionsHtml += `</div > </div > </body > <script type="text/javascript src=""></script> `; console.log(mdFiles.join("")); console.log(CollectionsHtml); res.send(CollectionsHtml); });
额外提醒
一定要加错误处理!不管用哪种方案,都要捕获readdir可能出现的错误(比如目录不存在、没有权限),不然服务器遇到异常可能会直接崩掉。
备注:内容来源于stack exchange,提问作者emkawley
相关产品推荐
相关产品推荐

