15

Node.js ではfs.createWriteStream、ローカル ファイルにデータを追加するメソッドを使用しています。ノードのドキュメントでは、drain使用時のイベントについて言及してfs.createWriteStreamいますが、私はそれを理解していません。

var stream = fs.createWriteStream('fileName.txt');
var result = stream.write(data);

上記のコードで、drain イベントをどのように使用できますか? 以下のイベントは適切に使用されていますか?

var data = 'this is my data';
if (!streamExists) {
  var stream = fs.createWriteStream('fileName.txt');
}

var result = stream.write(data);
if (!result) {
  stream.once('drain', function() {
    stream.write(data);
  });
}
4

4 に答える 4

32

このdrainイベントは、書き込み可能なストリームの内部バッファーが空になったときのものです。

これは、内部バッファーのサイズがそのhighWaterMarkプロパティを超えた場合にのみ発生します。これは、データ ソースからの読み取りを停止するまで、書き込み可能なストリームの内部バッファー内に格納できるデータの最大バイト数です。

このような問題の原因は、あるストリームからのデータ ソースの読み取りが、別のリソースへの書き込みよりも高速であることが原因である可能性があります。たとえば、次の 2 つのストリームがあります。

var fs = require('fs');

var read = fs.createReadStream('./read');
var write = fs.createWriteStream('./write');

ここで、ファイルreadが SSD 上にあり、500MB/s で読み取ることwriteができ、HDD 上にあると想像してください150MB/s。書き込みストリームが追いつかなくなり、内部バッファにデータを格納し始めます。バッファーが に到達するhighWaterMarkと (デフォルトでは 16KB)、書き込みが を返し始めfalse、ストリームは内部的にドレインをキューに入れます。内部バッファの長さが 0 になると、drainイベントが発生します。

ドレインの仕組みは次のとおりです。

if (state.length === 0 && state.needDrain) {
  state.needDrain = false;
  stream.emit('drain');
}

これらは、writeOrBuffer機能の一部であるドレインの前提条件です。

var ret = state.length < state.highWaterMark;
state.needDrain = !ret;

イベントがどのようにdrain使用されるかを確認するには、Node.js ドキュメントの例を参照してください。

function writeOneMillionTimes(writer, data, encoding, callback) {
  var i = 1000000;
  write();
  function write() {
    var ok = true;
    do {
      i -= 1;
      if (i === 0) {
        // last time!
        writer.write(data, encoding, callback);
      } else {
        // see if we should continue, or wait
        // don't pass the callback, because we're not done yet.
        ok = writer.write(data, encoding);
      }
    } while (i > 0 && ok);
    if (i > 0) {
      // had to stop early!
      // write some more once it drains
      writer.once('drain', write);
    }
  }
}

関数の目的は、書き込み可能なストリームに 1,000,000 回書き込むことです。変数okが true に設定され、が true の場合にのみループが実行されokます。ループの反復ごとに、 の値が の値にok設定され、stream.write()a が必要な場合は false が返されますdrain。false になるとok、イベント ハンドラーがdrain待機し、発生すると、書き込みを再開します。


特にコードに関してはdrain、ストリームを開いた直後に一度だけ書き込むため、イベントを使用する必要はありません。ストリームにはまだ何も書き込んでいないため、内部バッファーは空であり、drainイベントを発生させるには、少なくとも 16 KB のチャンクを書き込む必要があります。イベントは、書き込み可能なストリームdrainの設定よりも多くのデータを何度も書き込むためのものです。highWaterMark

于 2013-09-21T14:35:11.003 に答える
12

たとえば、低速のサーバーにローカル ファイルをアップロードするなど、帯域幅が大きく異なる 2 つのストリームを接続しているとします。(高速) ファイル ストリームは、(低速) ソケット ストリームがデータを消費するよりも速くデータを送信します。

この状況では、node.js は、低速ストリームがデータを処理する機会を得るまでデータをメモリに保持します。ファイルが非常に大きい場合、これは問題になる可能性があります。

これを回避するために、基になるシステム バッファがいっぱいになるとStream.write戻ります。false書き込みを停止すると、ストリームは後でdrainイベントを発行して、システム バッファーが空になり、再度書き込むことが適切であることを示します。

読み取り可能なストリームを使用pause/resumeして、読み取り可能なストリームの帯域幅を制御できます。

より良い:readable.pipe(writable)これを行うために使用できます。

編集: コードにバグがあります:write返される内容に関係なく、データは書き込まれています。再試行する必要はありません。あなたの場合、あなたはdata2回書いています。

次のようなものが機能します。

var packets = […],
    current = -1;

function niceWrite() {
  current += 1;

  if (current === packets.length)
    return stream.end();

  var nextPacket = packets[current],
      canContinue = stream.write(nextPacket);

  // wait until stream drains to continue
  if (!canContinue)
    stream.once('drain', niceWrite);
  else
    niceWrite();
}
于 2013-09-21T12:47:53.400 に答える