这里的底线是fs.appendFile() 是一个异步调用,您根本不会“等待”该调用在每次循环迭代中完成。这会产生许多后果,包括但不限于:
所以这里的简单解决方案是“等待”,一些现代语法糖让这变得简单:
const fs = require('mz/fs');
const x = 6551200;
(async function() {
try {
const fd = await fs.open('file','w');
for (let i = 0; i < x; i++) {
await fs.write(fd, `${i}\n`);
}
await fs.close(fd);
} catch(e) {
console.error(e)
} finally {
process.exit();
}
})()
这当然需要一段时间,但它不会在它工作时“炸毁”你的系统。
第一个简化的事情是获取mz 库,它已经将常见的nodejs 库与支持promise 的每个函数的现代化版本包装在一起。与使用回调相比,这将有助于清理语法。
接下来要意识到的是fs.appendFile() 在一次通话中如何“打开/写入/关闭”。这不是很好,所以你通常会做的只是open 然后write 循环中的字节,完成后你实际上可以close 文件句柄。
“糖”出现在现代版本中,尽管 “可能” 带有简单的承诺链,但它仍然不是那么易于管理。因此,如果您实际上没有支持 async/await 糖或“转换”此类代码的工具的 nodejs 环境,那么您也可以考虑使用带有普通回调的 asyncjs 库:
const Async = require('async');
const fs = require('fs');
const x = 6551200;
let i = 0;
fs.open('file','w',(err,fd) => {
if (err) throw err;
Async.whilst(
() => i < x,
callback => fs.write(fd,`${i}\n`,err => {
i++;
callback(err)
}),
err => {
if (err) throw err;
fs.closeSync(fd);
process.exit();
}
);
});
同样的基本原则适用于我们在继续之前“等待”每个回调完成。这里的whilst() helper 允许迭代直到满足测试条件,当然在数据传递给迭代器本身的回调之前不会进行下一次迭代。
还有其他方法可以解决这个问题,但对于“大循环”的迭代来说,这两种方法可能是最明智的。通过.reduce()“链接”等常见方法确实更适合您已经拥有的“合理”大小的数据数组,而在这里构建这种大小的数组本身就有问题。
例如,以下“工作”(至少在我的机器上)但它确实会消耗大量资源:
const fs = require('mz/fs');
const x = 6551200;
fs.open('file','w')
.then( fd =>
[ ...Array(x)].reduce(
(p,e,i) => p.then( () => fs.write(fd,`${i}\n`) )
, Promise.resolve()
)
.then(() => fs.close(fd))
)
.catch(e => console.error(e) )
.then(() => process.exit());
因此,本质上在内存中构建如此大的链然后让它解决实际上并不实际。您可以在此添加一些“治理”,但所示的主要两种方法要简单得多。
对于这种情况,您要么拥有可用的 async/await 糖,因为它在当前 LTS 版本的 Node (LTS 8.x) 中,或者我会坚持使用其他经过验证的真正“异步助手”来进行回调仅限于没有该支持的版本
您当然可以“开箱即用”最近几个版本的 nodejs 来“承诺”任何功能,因为Promise 在一段时间内已经成为一个全球性的东西:
const fs = require('fs');
await new Promise((resolve, reject) => fs.open('file','w',(err,fd) => {
if (err) reject(err);
resolve(fd);
});
所以确实没有必要仅仅为了做到这一点而导入库,但是这里作为示例给出的 mz 库可以为您完成所有这些工作。因此,是否引入其他依赖项完全取决于个人喜好。