web-dev-qa-db-ja.com

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

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

手始めに、単純なパイプチェーンを実行したい場合はどうしますか。

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

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

関連する質問:

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

transform

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


Where you might put a transform stream


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

  1. ストリームモジュールを含める
  2. transformクラスのインスタンス化(または継承)
  3. _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);
 });
  • 上記のコードサンプルは this post からのものです

ドメインの美しさは、スタックトレースを保持することです。ただし、イベントストリームもこれに適しています。

詳細については、 stream-handbook をご覧ください。かなり深いが、非常に便利であり、多くの有用なモジュールへの素晴らしいリンクを提供します。

199
mshell_lauren

ドメインは非推奨です。それらは必要ありません。

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

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つがエラーイベントを発生させた場合に、悪名高いキャッチされない例外が発生しないようにします。

23
Bent Cardan

ノード> = 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 .

19
shusson

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

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

.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

4
Vikas Gautam

エラーを伝播するために、変換ストリームの仕組みを作成し、そのコールバック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);
   });
1
Derek