ストリームでエラーを処理する正しい方法は何ですか?私はあなたが聞くことができる「エラー」イベントがあることをすでに知っていますが、arbitrarily意的に複雑な状況についてもう少し詳しく知りたいです。
手始めに、単純なパイプチェーンを実行したい場合はどうしますか。
input.pipe(transformA).pipe(transformB).pipe(transformC)...
そして、エラーを正しく処理するために、これらの変換の1つをどのように適切に作成しますか?
関連する質問:
transform
変換ストリームは読み取りと書き込みの両方が可能なため、本当に良い「中間」ストリームです。このため、これらはthrough
ストリームと呼ばれることもあります。この方法では、これらは二重のSteamに似ていますが、単に送信するのではなく、データを操作するためのNiceインターフェイスを提供します。変換ストリームの目的は、ストリームを介してパイプされるデータを操作することです。たとえば、いくつかの非同期呼び出しを実行したり、いくつかのフィールドを取得したり、いくつかの事柄を再マップしたりすることができます。
変換ストリームの作成方法については、 here および here を参照してください。あなたがしなければならないのは:
_transform
を取る(chunk, encoding, callback)
メソッドを実装します。チャンクはデータです。 objectMode = true
で作業している場合、ほとんどの場合、エンコードについて心配する必要はありません。チャンクの処理が完了すると、コールバックが呼び出されます。その後、このチャンクは次のストリームにプッシュされます。
本当に簡単にストリームを処理できるニースヘルパーモジュールが必要な場合は、 through2 をお勧めします。
エラー処理については、読み続けてください。
パイプ
パイプチェーンでは、エラーの処理は確かに重要です。 this thread .pipe()によると、エラーを転送するようには構築されていません。のようなもの...
var a = createStream();
a.pipe(b).pipe(c).on('error', function(e){handleError(e)});
...ストリームのエラーのみをリッスンしますc
。エラーイベントがa
で発行された場合、それは渡されず、実際にはスローされます。これを正しく行うには:
var a = createStream();
a.on('error', function(e){handleError(e)})
.pipe(b)
.on('error', function(e){handleError(e)})
.pipe(c)
.on('error', function(e){handleError(e)});
現在、2番目の方法はより冗長ですが、少なくともエラーが発生した場所のコンテキストを保持できます。これは通常良いことです。
ただし、宛先でエラーをキャプチャするだけで、発生場所をあまり気にしない場合は event-stream です。
end
エラーイベントが発生すると、終了イベントは(明示的に)発生しません。エラーイベントを発行すると、ストリームが終了します。
ドメイン
私の経験では、ほとんどの場合、ドメインは本当にうまく機能します。未処理のエラーイベントがある場合(つまり、リスナーのないストリームでエラーが発生している場合)、サーバーがクラッシュする可能性があります。さて、上記の記事が指摘しているように、すべてのエラーを適切にキャッチするドメインでストリームをラップできます。
var d = domain.create();
d.on('error', handleAllErrors);
d.run(function() {
fs.createReadStream(tarball)
.pipe(gzip.Gunzip())
.pipe(tar.Extract({ path: targetPath }))
.on('close', cb);
});
ドメインの美しさは、スタックトレースを保持することです。ただし、イベントストリームもこれに適しています。
詳細については、 stream-handbook をご覧ください。かなり深いが、非常に便利であり、多くの有用なモジュールへの素晴らしいリンクを提供します。
ドメインは非推奨です。それらは必要ありません。
この質問では、変換または書き込み可能の区別はそれほど重要ではありません。
mshell_laurenの答えは素晴らしいですが、代わりに、エラーの可能性があると思われる各ストリームでエラーイベントを明示的にリッスンすることもできます。必要に応じてハンドラー関数を再利用します。
var a = createReadableStream()
var b = anotherTypeOfStream()
var c = createWriteStream()
a.on('error', handler)
b.on('error', handler)
c.on('error', handler)
a.pipe(b).pipe(c)
function handler (err) { console.log(err) }
そうすることで、これらのストリームの1つがエラーイベントを発生させた場合に、悪名高いキャッチされない例外が発生しないようにします。
ノード> = v10.0.0を使用している場合は、 stream.pipeline および stream.finished を使用できます。
例えば:
const { pipeline, finished } = require('stream');
pipeline(
input,
transformA,
transformB,
transformC,
(err) => {
if (err) {
console.error('Pipeline failed', err);
} else {
console.log('Pipeline succeeded');
}
});
finished(input, (err) => {
if (err) {
console.error('Stream failed', err);
} else {
console.log('Stream is done reading');
}
});
詳細については、こちらをご覧ください github PR .
単純な関数を使用して、チェーン全体からのエラーを右端のストリームに伝播できます。
function safePipe (readable, transforms) {
while (transforms.length > 0) {
var new_readable = transforms.shift();
readable.on("error", function(e) { new_readable.emit("error", e); });
readable.pipe(new_readable);
readable = new_readable;
}
return readable;
}
次のように使用できます:
safePipe(readable, [ transform1, transform2, ... ]);
.on("error", handler)
はストリームエラーのみを処理しますが、カスタムトランスフォームストリームを使用している場合、.on("error", handler)
は_transform
関数内で発生するエラーをキャッチしません。したがって、アプリケーションフローを制御するために次のようなことができます。
_transform
関数のthis
キーワードは、Stream
自体であるEventEmitter
を参照します。そのため、以下のtry catch
を使用してエラーをキャッチし、後でそれらをカスタムイベントハンドラーに渡すことができます。
// CustomTransform.js
CustomTransformStream.prototype._transform = function (data, enc, done) {
var stream = this
try {
// Do your transform code
} catch (e) {
// Now based on the error type, with an if or switch statement
stream.emit("CTError1", e)
stream.emit("CTError2", e)
}
done()
}
// StreamImplementation.js
someReadStream
.pipe(CustomTransformStream)
.on("CTError1", function (e) { console.log(e) })
.on("CTError2", function (e) { /*Lets do something else*/ })
.pipe(someWriteStream)
これにより、ロジックハンドラとエラーハンドラを分離できます。また、一部のエラーのみを処理し、他のエラーを無視することもできます。
UPDATE
代替:RXJS Observable
エラーを伝播するために、変換ストリームの仕組みを作成し、そのコールバックdone
を引数で呼び出してNode.jsパターンを使用します。
var transformStream1 = new stream.Transform(/*{objectMode: true}*/);
transformStream1.prototype._transform = function (chunk, encoding, done) {
//var stream = this;
try {
// Do your transform code
/* ... */
} catch (error) {
// nodejs style for propagating an error
return done(error);
}
// Here, everything went well
done();
}
// Let's use the transform stream, assuming `someReadStream`
// and `someWriteStream` have been defined before
someReadStream
.pipe(transformStream1)
.on('error', function (error) {
console.error('Error in transformStream1:');
console.error(error);
process.exit(-1);
})
.pipe(someWriteStream)
.on('close', function () {
console.log('OK.');
process.exit();
})
.on('error', function (error) {
console.error(error);
process.exit(-1);
});