node.jsストリームでのエラー処理


164

ストリームのエラーを処理する正しい方法は何ですか?あなたが聞くことができる「エラー」イベントがあることはすでに知っていますが、私は恣意的に複雑な状況についていくつかの詳細を知りたいです。

まず、単純なパイプチェーンを作成する場合はどうしますか。

input.pipe(transformA).pipe(transformB).pipe(transformC)...

そして、エラーが正しく処理されるように、これらの変換の1つをどのように適切に作成しますか?

さらに関連する質問:

  • エラーが発生した場合、「終了」イベントはどうなりますか?解雇されることはありませんか?それは時々解雇されますか?変換/ストリームに依存していますか?ここでの基準は何ですか?
  • パイプを介してエラーを伝播するメカニズムはありますか?
  • ドメインはこの問題を効果的に解決しますか?例はいいでしょう。
  • 「エラー」イベントから発生するエラーにはスタックトレースがありますか?時々?決して?それらから取得する方法はありますか?

1
これは簡単なことではありません。Promiseフレームワークはそれをより簡単にします
slezica

27
残念ながら、約束/将来は実際にストリームを支援することはできません...
BT

回答:


222

変身

変換ストリームは読み取りと書き込みの両方が可能なため、非常に優れた「中間」ストリームです。このため、throughストリームと呼ばれることもあります。これらは、データを送信するだけでなく、データを操作するための優れたインターフェースを提供することを除いて、この点で二重ストリームに似ています。変換ストリームの目的は、データがストリームを介してパイプされるときにデータを操作することです。たとえば、いくつかの非同期呼び出しを実行したり、いくつかのフィールドを派生させたり、いくつかのものをリマップしたりすることができます。


変換ストリームを配置する場所


変換ストリームの作成方法については、ここここを参照ください。あなたがしなければならないすべては:

  1. ストリームモジュールを含める
  2. Transformクラスをインスタンス化(または継承)
  3. を受け取る_transformメソッドを実装し(chunk, encoding, callback)ます。

チャンクはあなたのデータです。で作業している場合は、ほとんどの場合、エンコーディングについて心配する必要はありませんobjectMode = true。チャンクの処理が完了すると、コールバックが呼び出されます。次に、このチャンクは次のストリームにプッシュされます。

本当に簡単にストリームを実行できる素晴らしいヘルパーモジュールが必要な場合は、through2をお勧めします。

エラー処理については、読み続けてください。

パイプ

パイプチェーンでは、エラーの処理は確かに重要です。このスレッドによると .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が役立ちます

終わり

エラーイベントが発生しても、終了イベントは(明示的に)発生しません。エラーイベントの発行により、ストリームが終了します。

ドメイン

私の経験では、ほとんどの場合、ドメインは本当にうまく機能しています。未処理のエラーイベントがある場合(つまり、リスナーなしでストリームでエラーが発生する場合)、サーバーがクラッシュする可能性があります。これで、上記の記事で指摘したように、すべてのエラーを適切にキャッチするドメインでストリームをラップできます。

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);
 });

ドメインの優れた点は、スタックトレースが保持されることです。イベントストリームもこれをうまく行います。

詳しくは、ストリームハンドブックをご覧ください。かなり詳細ですが、非常に便利であり、多くの便利なモジュールへの素晴らしいリンクを提供します。


それは本当に素晴らしい情報です、ありがとう!変換ストリームを作成する理由と、それがなぜ私の質問に関係するのかについて少し説明してください。
BT

確かに、あなたがそれについて尋ねたので、私はそれが関連していると考えましたが。)
mshell_lauren

1
isaccsでGoogleグループにこれを投稿する-nodejs:groups.google.com/d/msg/nodejs/lJYT9hZxFu0/L59CFbqWGyYJ(grokbaseではない)
jpillora

この答えは完全に書かれています。私はドメインの提案を調査します-それは私が探していた種類のソリューションのようです。
セミコロン2015年

12
.on('error')ハンドラを無名関数でラップする必要がないことに注意してください。つまり、次のようにするa.on('error', function(e){handleError(e)})ことができますa.on('error', handleError)
timoxley

28

node> = 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を参照してください。


1
finishedしかし、pipelineすでにコールバックがあるのに、なぜ使用するのですか?
Marcos Pereira

4
パイプラインと個々のストリーム間で異なる方法でエラーを処理したい場合があります。
shusson

25

ドメインは非推奨です。あなたはそれらを必要としません。

この質問では、変換と書き込み可能の区別はそれほど重要ではありません。

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つがエラーイベントを発生させた場合に、悪名高いキャッチされていない例外を防ぐことができます


3
笑3つの異なるエラーイベントを処理する楽しみを持っているし、3つの異なるストリーミングライブラリを作成した人がエラー処理を正しく実装したことを祈る
Alexander Mills

4
@Alex Mills 1)3つのイベントを処理する際の問題は何ですか。また、タイプが同じであるのに、なぜ「異なる」のですか?error各イベントが異なるという事実で解決できます。2)ネイティブのNode.js機能以外に、上記のどのストリーミングライブラリが記述されていますか?そして、3)明らかに誰もがすでにそこにあるものの上に追加のエラーハンドラーをアタッチできるようにするときに、なぜ内部でイベントを処理する方法が重要なのですか?
16

10

チェーン全体のエラーは、単純な関数を使用して右端のストリームに伝播できます。

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, ... ]);

5

.on("error", handler)ストリームエラーのみを処理しますが、カスタム変換ストリームを使用している場合は、関数.on("error", handler)内で発生するエラーをキャッチしないでください_transform。したがって、アプリケーションフローを制御するために、次のようなことができます。

this_transform関数内のキーワードは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監視可能


4

マルチパイプパッケージを使用して、複数のストリームを1つの二重ストリームに結合します。エラーを1か所で処理します。

const pipe = require('multipipe')

// pipe streams
const stream = pipe(streamA, streamB, streamC) 


// centralized error handling
stream.on('error', fn)

1

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);
   });

うーん、あなたはすべてのストリームプロセッサがこのように構築されている場合、エラーが伝播するのですか?
BT

-2

呼び出しでコードが既に終了した後にスローされるため、ストリームで発生したエラーはキャプチャしようとしません。ドキュメントを参照できます:

https://nodejs.org/dist/latest-v10.x/docs/api/errors.html


おかげで、これは質問にまったく答えません。
BT

40ページのドキュメントを私に与えることは役に立ちません。その巨大なページで私は何を参照すべきだと思いますか?また、私の質問を読みましたか?私の質問は、「ストリームでcatchが機能するか」ではありません。ストリーム処理パイプラインからのエラーなど、try-catchが非同期エラーで機能しないことはすでに承知しています。
BT
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.