在尝试使用Node.JS流时,我遇到了一个有趣的难题。当输入(可读)流推送更多数据时,目标(可写)关心我无法正确应用背压。
我尝试的两种方法是从Writable.prototype._write
返回false并保留对Readable的引用,以便我可以从Writable中调用Readable.pause()
。这两种解决方案都没有帮助我解释。
在我的练习中(你可以查看the full source as a Gist)我有三个流:
util.inherits(PasscodeGenerator, stream.Readable);
function PasscodeGenerator(prefix) {
stream.Readable.call(this, {objectMode: true});
this.count = 0;
this.prefix = prefix || '';
}
PasscodeGenerator.prototype._read = function() {
var passcode = '' + this.prefix + this.count;
if (!this.push({passcode: passcode})) {
this.pause();
this.once('drain', this.resume.bind(this));
}
this.count++;
};
我认为来自this.push()
的返回代码足以自行暂停并等待drain
事件恢复。
util.inherits(Hasher, stream.Transform);
function Hasher(hashType) {
stream.Transform.call(this, {objectMode: true});
this.hashType = hashType;
}
Hasher.prototype._transform = function(sample, encoding, next) {
var hash = crypto.createHash(this.hashType);
hash.setEncoding('hex');
hash.write(sample.passcode);
hash.end();
sample.hash = hash.read();
this.push(sample);
next();
};
只需将密码的哈希值添加到对象中即可。
util.inherits(SampleConsumer, stream.Writable);
function SampleConsumer(max) {
stream.Writable.call(this, {objectMode: true});
this.max = (max != null) ? max : 10;
this.count = 0;
}
SampleConsumer.prototype._write = function(sample, encoding, next) {
this.count++;
console.log('Hash %d (%s): %s', this.count, sample.passcode, sample.hash);
if (this.count < this.max) {
next();
} else {
return false;
}
};
在这里,我希望尽可能快地使用数据,直到达到最大样本数,然后结束流。我尝试使用this.end()
代替return false
,但这导致了在结束问题后调用的可怕的写入。如果样本大小很小,则返回false会停止所有内容但是当它很大时,我会收到内存不足错误:
FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - process out of memory
Aborted (core dumped)
理论上,根据this SO answer,写入流将返回false,导致流缓冲,直到缓冲区已满(默认情况下为objectMode
为16),最终Readable将其称为“#”。 this.pause()
方法。但是16 + 16 + 16 = 48
;缓冲区中的48个对象,直到事情充满,系统堵塞。实际上更少,因为没有涉及克隆,所以在它们之间传递的对象是相同的参考。这不是指内存中只有16个物体,直到高水位标记停止一切?
最后我意识到我可以使用Writable引用Readable来调用它使用闭包的暂停方法。但是,此解决方案意味着Writable流非常了解另一个对象。我必须传递参考资料:
var foo = new PasscodeGenerator('foobar');
foo
.pipe(new Hasher('md5'))
.pipe(new SampleConsumer(samples, foo));
这对于流如何工作感觉不合时宜。我认为背压足以导致Writable停止Readable推送数据并防止内存不足错误。
一个类似的例子是Unix head
命令。在Node I中实现它会假设目标可以结束而不是忽略导致源继续推送数据,即使目标有足够的数据来满足文件的开头部分。
如何以惯用方式构建自定义流,以便在目标准备好结束时,源流不会尝试推送更多数据?
答案 0 :(得分:1)
这是一个known issue,内部调用_read()
。由于您的_read()
始终是同步/立即推送,因此内部流实现可以在正确的条件下进入循环。 _read()
_read()
实施是generally expected来执行某种异步I / O(例如从磁盘或网络读取)。
此解决方法(如上面的链接中所述)是在某些时候使PasscodeGenerator.prototype._read = function(n) {
var passcode = '' + this.prefix + this.count;
var self = this;
// `setImmediate()` delays the push until the beginning
// of the next tick of the event loop
setImmediate(function() {
self.push({passcode: passcode});
});
this.count++;
};
异步至少。您也可以在每次调用它时使其异步:
hybrid_property