以下是代码:
var fs = require('fs')
for(let i=0;i<6551200;i++){
fs.appendFile('file',i,function(err){
})
}
当我运行此代码时,几秒钟后,它会显示:
FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory
但文件中没有任何内容!
我的问题是:
感谢提前。
答案 0 :(得分:1)
这里的底线是fs.appendFile()
是一个异步调用而你根本就没有等待&#34;等待&#34;调用在每次循环迭代时完成。这有许多后果,包括但不限于:
回调在解决之前会继续分配,这会导致 &#34;堆内存不足&#34; 最终到达。< / p>
您正在与文件句柄竞争,因为您正在使用的功能实际上是打开/写入/关闭给定的文件,如果您不等待每个回合都这样做,那么您& #39;只是简单地发生冲突。
因此,这里的简单解决方案是 &#34;等待&#34; ,并且一些现代语法糖使这很容易:
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();
}
})()
这当然需要一段时间,但它不会“爆炸”#34;你的系统在它工作的同时。
第一个简化的事情是获取mz
库,它已经包含了常见的nodejs库以及支持promises的每个函数的现代化版本。与使用回调相比,这将有助于清理语法。
接下来要认识到的是fs.appendFile()
关于它是如何&#34;打开/写入/关闭&#34;一次通话。这不是很好,所以你通常做的只是open
然后write
循环中的字节,当它完成时你实际上可以close
文件句柄
那&#34;糖&#34;现代版本,虽然&#34;可能&#34; 具有明确的承诺链接,但它仍然不是那么易于管理。因此,如果您实际上没有一个nodejs环境支持async/await
糖或工具来转换&#34;这样的代码,那么你可以考虑使用asyncjs
libary和普通的回调:
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();
}
);
});
同样的基本原则适用于我们&#34;等待&#34;在继续之前完成每个回调。这里的whilst()
帮助器允许迭代直到满足测试条件,当然不会进行下一次迭代,直到数据被传递给迭代器本身的回调。
还有其他方法可以解决这个问题,但对于一个大型循环来说,这可能是最合理的两个方法。迭代。常见的方法,如&#34;链接&#34;通过.reduce()
真的更适合'#34;合理的&#34;您已经拥有的大小数据,并且在这里构建这种大小的数组具有它自己的固有问题。
例如,以下&#34;工作&#34; (至少在我的机器上)但它确实消耗了大量资源:
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());
因此,在内存中构建如此大的链然后允许它解决它真的不那么实用。你可以在这上面加上一些&#34;治理&#34; ,但是显示的主要两种方法要简单得多。
对于那种情况,那么你可以获得async/await
糖,因为它在当前LTS版本的Node(LTS 8.x)中,或者我会坚持使用其他尝试过的和真正的&#34; async helpers& #34;对于您被限制为没有该支持的版本的回调
你当然可以&#34; promisify&#34;任何与nodejs的最后几个版本相关的函数#34;开箱即用&#34;就像它在哪里一样,Promise
已经成为一个全球性的东西了一段时间:
const fs = require('fs');
await new Promise((resolve, reject) => fs.open('file','w',(err,fd) => {
if (err) reject(err);
resolve(fd);
});
所以真的没有必要只是为了那样导入库,但是这里给出的mz
库为你做了所有这些。因此,在引入其他依赖项时,这取决于个人偏好。
答案 1 :(得分:0)
1 - 文件为空,因为fs.append调用都没有完成,之前Node.JS进程被破坏。
2 - Node.JS堆内存是有限的,并存储回调,直到它返回,而不仅仅是“i”变量。
3 - 你可以尝试使用promises来做到这一点。
"use strict";
const Bluebird = require('bluebird');
const fs = Bluebird.promisifyAll(require('fs'));
let promisses = [];
for (let i = 0; i < 6551200; i++){
promisses.push(fs.appendFileAsync('file', i + '\n'));
}
Bluebird.all(promisses)
.then(data => {
console.log(data, 'End.');
})
.catch(e => console.error(e));
但是没有逻辑可以避免这个大循环的堆内存错误。您可以增加Node.JS Heep Memory,或者以合理的方式,获取间隔的数据块:
'use strict';
const fs = require('fs');
let total = 6551200;
let interval = setInterval(() => {
fs.appendFile('file', total + '\n', () => {});
total--;
if (total < 1) {
clearInterval(interval);
}
}, 1);
答案 2 :(得分:0)
Javascript是一种单线程语言,这意味着您的代码可以在当时执行一个函数。因此,当您执行异步功能时,它将排除&#34;排队&#34;在下一个要执行的堆栈中。
所以在您的代码中,您正在向堆栈发送6551200个调用,这当然会在开始工作之前使您的应用程序崩溃&#34; appendFile&#34;他们中的任何一个。
你可以通过将循环分成更小的循环,使用async和等待函数或迭代器来实现你想要的。
如果您尝试实现的内容与代码一样简单,则可以使用以下内容:
const fs = require("fs");
function SomeTask(i=0){
fs.appendFile('file',i,function(err){
//err in the write function
if(err) console.log("Error", err);
//check if you want to continue (loop)
if(i<6551200) return SomeTask(i);
//on finish
console.log("done");
});
}
SomeTask();
在上面的代码中,你写了一行,完成后,你调用下一行。 此函数仅供基本使用,需要重构并使用Javascript迭代器进行高级用法check out Iterators and generators on MDN web docs