节点的流的基本行为之一是在写入暂停的流时阻塞,并且任何非管道流都被阻塞。
在此示例中,创建的PassThrough
没有通过管道传递到其创建事件循环中的任何内容。有人希望在此PassThrough
上运行的所有管道都将阻塞,直到它被管道传输/附加了数据事件为止,但事实并非如此。
pipeline
回调,但不消耗任何空间。
const {promises: pFs} = require('fs');
const fs = require('fs');
const {PassThrough} = require('stream');
const {pipeline: pipelineCb} = require('stream');
const util = require('util');
const pipeline = util.promisify(pipelineCb);
const path = require('path');
const assert = require('assert');
/**
* Start a test ftp server
* @param {string} outputPath
* @return {Promise<void>}
*/
function myCreateWritableStream (outputPath) {
// The stream is created in paused mode -> should block until piped
const stream = new PassThrough();
(async () => {
// Do some stuff (create directory / check space / connect...)
await new Promise(resolve => setTimeout(resolve, 500));
console.log('piping passThrough to finale output');
// Consume the stream
await pipeline(stream, fs.createWriteStream(outputPath));
console.log('passThrough stream content written');
})().catch(e => {
console.error(e);
stream.emit('error', e);
});
return stream;
}
/**
* Main test function
* @return {Promise<void>}
*/
async function main () {
// Prepare the test directory with a 'tmp1' file only
const smallFilePath = path.join(__dirname, 'tmp1');
const smallFileOut = path.join(__dirname, 'tmp2');
await Promise.all([
pFs.writeFile(smallFilePath, 'a small content'),
pFs.unlink(smallFileOut).catch(e => assert(e.code === 'ENOENT'))
]);
// Duplicate the tmp1 file to tmp2
await pipeline([
fs.createReadStream(smallFilePath),
myCreateWritableStream(smallFileOut)
]);
console.log('pipeline ended');
// Check content
const finalContent = await pFs.readdir(__dirname);
console.log('directory content');
console.log(finalContent.filter(file => file.startsWith('tmp')));
}
main().catch(e => {
process.exitCode = 1;
console.error(e);
});
此代码输出以下行:
pipeline ended
directory content
[ 'tmp1' ]
piping passThrough to finale output
passThrough stream content written
如果pipeline
确实等待流结束,那么输出将是以下内容:
piping passThrough to finale output
passThrough stream content written
pipeline ended
directory content
[ 'tmp1', 'tmp2' ]
您如何解释这种行为?
答案 0 :(得分:1)
我认为API不能保证您在此处找到所需的保证。
stream.pipeline
在所有数据写完后调用其回调。由于数据已被写入新的Transform流(您的Passthrough),并且该流还没有放置数据的位置,因此只需将其存储在流的内部缓冲区中即可。这对于管道已经足够了。
如果您要读取一个足够大的文件,并填充了Transform流的缓冲区,则stream backpressure可以在正在读取文件的可读内容上自动触发pause()
。转换流耗尽后,它将自动unpause()
可读,以便恢复数据流。
我认为您的示例有两个错误的假设:
(1)可以暂停转换流。根据{{3}}的说法,暂停任何通过管道传输到目标的流都是无效的,因为一旦通过管道传输的目标请求更多数据,该流将立即自动暂停。此外,暂停的转换流仍会读取数据!暂停的流只是不写入数据。
(2)流水线下方的停顿以某种方式传播到流水线的前端,并导致数据停止流动。如果是由背压引起的,则仅是正确的,这意味着您将需要触发节点检测到完整的内部缓冲区。
在使用管道时,最好假定您对两个最远的端部有手动控制,但不一定要控制中间的任何一个。 (您可以手动pipe()
和unpipe()
来连接和断开中间流,但不能暂停它们。)