Node.js v0.11.11 マニュアル & ドキュメンテーション


Domain#

Stability: 2 - Unstable

ドメインは複数の異なる I/O 操作を一つのグループとして扱う方法を 提供します。 もし EventEmitter またはコールバックがドメインに登録されると、 'error' がイベントが発生したり例外がスローされた場合、 process.on('uncaughtException') ハンドラでエラーのコンテキストが失われたり、 プログラムがエラーコードと共に即座に終了する代わりに、 ドメインオブジェクトに通知されます

この機能は Node バージョン 0.8 からの新しいものです。 これはファーストパスであり、将来のバージョンで大きく変更されると予想されます。 是非これを使ってフィードバックをしてください。

これらは実験的であるため、ドメインの機能は domain モジュールが少なくとも 一回はロードされるまで無効になっています。 デフォルトではドメインは作成されず、デフォルトで登録もされません。 それは既存のプログラムに悪影響を与えることを防ぐために設計されています。 将来の Node.js バージョンではデフォルトで有効になることが期待されます。

Warning: Don't Ignore Errors!#

ドメインのエラーハンドラは、エラーが発生した時に プロセスを終了する代わりにはなりません。

JavaScript において「throw」がどのように働くかという性質により、 参照のリークや未定義の脆弱な状態を作ることなく「中断したところを取得する」 方法はほとんどありません。

スローされたエラーに対処するもっとも安全な方法はプロセスを終了することです。 もちろん、通常の Web サーバは多くの接続をオープンしていており、 他の誰かによって引き起こされたエラーのためにそれらをシャットダウンすることは 合理的ではありません。

よりよいアプローチは、エラーを引き起こしたリクエストにエラーを応答し、 それ以外の接続が正常に終了するまでの間、ワーカは新しいリクエストのリスニングを 止めることです。

この方法では、domain はクラスタモジュールと手を取り合う利用方法により、 ワーカプロセスがエラーに遭遇した場合に新しいワーカをフォークできます。 複数のマシンにスケールする node プログラムでは、 終端のプロキシやサービスレジストリは障害に注意し、 それに応じて対処することができます。

たとえば、これはいいアイディアではありません:

// XXX WARNING!  BAD IDEA!

var d = require('domain').create();
d.on('error', function(er) {
  // The error won't crash the process, but what it does is worse!
  // Though we've prevented abrupt process restarting, we are leaking
  // resources like crazy if this ever happens.
  // This is no better than process.on('uncaughtException')!
  console.log('error, but oh well', er.message);
});
d.run(function() {
  require('http').createServer(function(req, res) {
    handleRequest(req, res);
  }).listen(PORT);
});

domain の利用と、プログラムを複数のワーカプロセスに分割することによる 復元力により、とても安全にエラーを扱う、より適切な対処をすることができます。

// Much better!

var cluster = require('cluster');
var PORT = +process.env.PORT || 1337;

if (cluster.isMaster) {
  // In real life, you'd probably use more than just 2 workers,
  // and perhaps not put the master and worker in the same file.
  //
  // You can also of course get a bit fancier about logging, and
  // implement whatever custom logic you need to prevent DoS
  // attacks and other bad behavior.
  //
  // See the options in the cluster documentation.
  //
  // The important thing is that the master does very little,
  // increasing our resilience to unexpected errors.

  cluster.fork();
  cluster.fork();

  cluster.on('disconnect', function(worker) {
    console.error('disconnect!');
    cluster.fork();
  });

} else {
  // the worker
  //
  // This is where we put our bugs!

  var domain = require('domain');

  // See the cluster documentation for more details about using
  // worker processes to serve requests.  How it works, caveats, etc.

  var server = require('http').createServer(function(req, res) {
    var d = domain.create();
    d.on('error', function(er) {
      console.error('error', er.stack);

      // Note: we're in dangerous territory!
      // By definition, something unexpected occurred,
      // which we probably didn't want.
      // Anything can happen now!  Be very careful!

      try {
        // make sure we close down within 30 seconds
        var killtimer = setTimeout(function() {
          process.exit(1);
        }, 30000);
        // But don't keep the process open just for that!
        killtimer.unref();

        // stop taking new requests.
        server.close();

        // Let the master know we're dead.  This will trigger a
        // 'disconnect' in the cluster master, and then it will fork
        // a new worker.
        cluster.worker.disconnect();

        // try to send an error to the request that triggered the problem
        res.statusCode = 500;
        res.setHeader('content-type', 'text/plain');
        res.end('Oops, there was a problem!\n');
      } catch (er2) {
        // oh well, not much we can do at this point.
        console.error('Error sending 500!', er2.stack);
      }
    });

    // Because req and res were created before this domain existed,
    // we need to explicitly add them.
    // See the explanation of implicit vs explicit binding below.
    d.add(req);
    d.add(res);

    // Now run the handler function in the domain.
    d.run(function() {
      handleRequest(req, res);
    });
  });
  server.listen(PORT);
}

// This part isn't important.  Just an example routing thing.
// You'd put your fancy application logic here.
function handleRequest(req, res) {
  switch(req.url) {
    case '/error':
      // We do some async stuff, and then...
      setTimeout(function() {
        // Whoops!
        flerb.bark();
      });
      break;
    default:
      res.end('ok');
  }
}

Additions to Error objects#

どんな場合でも、ドメインに送られた Error オブジェクトは いくつかのフィールドが加えられます。

  • error.domain このエラーを最初に捕まえたドメイン。
  • error.domainEmitter このエラーオブジェクトと共に error イベントを 生成した EventEmitter。
  • error.domainBound ドメインに束縛されたコールバック関数で、 その第 1 引数にエラーが渡されたもの。
  • error.domainThrown エラーがスローされたか、EventEmitter から生成されたか、 それとも束縛されたコールバック関数に渡されたかを示す boolean 値。

Implicit Binding#

一度ドメインが作成されると、全ての 新しい EventEmitter オブジェクト (ストリームオブジェクトやリクエスト、レスポンス、その他を含む) は、 それが作成された時点でアクティブなドメインに暗黙的に束縛されます。

加えて、コールバックが低水準のイベントループ要求 (例えば fs.open()、 あるいは他のコールバックを受け取るメソッド) もアクティブなドメインに 束縛されます。 もしそれらが例外をスローすると、ドメインはそれを捕まえます。

必要以上にメモリを消費するのを防ぐため、ドメインオブジェクトそれ自身は 暗黙的にアクティブドメインの子として暗黙的には追加されません。 それをすれば、リクエストやレスポンスオブジェクトがきちんと GC されることを あまりにも簡単に妨害してしまうでしょう。

もしネストしたドメインを子として他のドメインに 加えたければ 明示的にそれを加えなければなりません。

暗黙的なドメインはスローされたエラーや 'error' イベントを、 ドメインの 'error' イベントにルーティングしますが、 その EventEmitter をドメインに登録しないので、domain.dispose() は EventEmitter をシャットダウンしません。 暗黙的なバインディングはスローされた例外と 'error' イベントにだけ 注意を払います。

Explicit Binding#

時には、使用中のドメインは特定の EventEmitter に使用されるべきではありません。 あるいは、EventEmitter はあるドメインのコンテキスト中で作成されますが、 その他のドメインに結びつけられるべきかもしれません。

例えば、HTTP サーバで使われるドメインが一つあるとしても、 おそらくリクエスト毎に独立したドメインを持ちたいでしょう。

これは明示的なバインディングによって可能となります。

例:

// create a top-level domain for the server
var serverDomain = domain.create();

serverDomain.run(function() {
  // server is created in the scope of serverDomain
  http.createServer(function(req, res) {
    // req and res are also created in the scope of serverDomain
    // however, we'd prefer to have a separate domain for each request.
    // create it first thing, and add req and res to it.
    var reqd = domain.create();
    reqd.add(req);
    reqd.add(res);
    reqd.on('error', function(er) {
      console.error('Error', er, req.url);
      try {
        res.writeHead(500);
        res.end('Error occurred, sorry.');
      } catch (er) {
        console.error('Error sending 500', er, req.url);
      }
    });
  }).listen(1337);
});

domain.create()#

  • return: Domain

新しいドメインオブジェクトを返します。

Class: Domain#

ドメインクラスはエラーのルーティングや捕まえられなかった例外をアクティブな ドメインにルーティングする機能をカプセル化します。

ドメインは EventEmitter の子クラスです。 これが捕まえたエラーを扱いたければ、'error' イベントを監視してください。

domain.run(fn)#

  • fn Function

与えられた関数をこのドメインのコンテキストで実行します。 このコンテキストで作成される全ての EventEmitter、タイマ、そして低水準の要求は 暗黙的にバインドされます。

これはドメインを使用するもっとも一般的な方法です。

例:

var d = domain.create();
d.on('error', function(er) {
  console.error('Caught error!', er);
});
d.run(function() {
  process.nextTick(function() {
    setTimeout(function() { // simulating some various async stuff
      fs.open('non-existent file', 'r', function(er, fd) {
        if (er) throw er;
        // proceed...
      });
    }, 100);
  });
});

この例では、プログラムはクラッシュせずに d.on('error') ハンドラが 呼び出されます。

domain.members#

  • Array

このドメインに明示的に加えられたタイマまたは EventEmitter の配列です。

domain.add(emitter)#

  • emitter {EventEmitter | Timer} ドメインに加えられる EventEmitter またはタイマ

明示的に EventEmitter をドメインに追加します。 この EventEmitter から呼ばれたどのイベントハンドラがエラーをスローしても、 あるいはこの EventEmitter が 'error' イベントを発生しても、 暗黙的にバインディングされたのと同様、それはこのドメインの 'error' イベントにルーティングされます。

これは同様に setIntervalu および setTimeout から返されるタイマでも 働きます。それらのコールバック関数がエラーをスローすると、それは ドメインの 'error' ハンドに捕まえられます。

もしタイマまたは EventEmitter が既に他のドメインに束縛されていた場合、 それは元のドメインから削除され、代わりにこのドメインに束縛されます。

domain.remove(emitter)#

  • emitter {EventEmitter | Timer} このドメインから削除される EventEmitter またはタイマ

これは domain.add(emitter) と対照的です。指定された EventEmitter を ドメインから削除します。

domain.bind(callback)#

  • callback {Function} コールバック関数
  • return: {Function} 束縛された関数

返される関数は与えられたコールバック関数のラッパーです。 返された関数が呼び出されると、スローされたエラーはドメインの 'error' イベントにルーティングされます。

Example#

var d = domain.create();

function readSomeFile(filename, cb) {
  fs.readFile(filename, 'utf8', d.bind(function(er, data) {
    // if this throws, it will also be passed to the domain
    return cb(er, data ? JSON.parse(data) : null);
  }));
}

d.on('error', function(er) {
  // an error occurred somewhere.
  // if we throw it now, it will crash the program
  // with the normal line number and stack message.
});

domain.intercept(callback)#

  • callback {Function} コールバック関数
  • return: {Function} インターセプトされた関数

このメソッドはほとんど domain.bind(callback) と同じです。 ただし、スローされたエラーを捕まえることに加えて、関数に渡される最初の引数が Error オブジェクトの場合もインターセプトします。

これは、一般的な if (er) return callback(er); パターンを一カ所で単一の エラーハンドラに置き換えることができます。

Example#

var d = domain.create();

function readSomeFile(filename, cb) {
  fs.readFile(filename, 'utf8', d.intercept(function(data) {
    // note, the first argument is never passed to the
    // callback since it is assumed to be the 'Error' argument
    // and thus intercepted by the domain.

    // if this throws, it will also be passed to the domain
    // so the error-handling logic can be moved to the 'error'
    // event on the domain instead of being repeated throughout
    // the program.
    return cb(null, JSON.parse(data));
  }));
}

d.on('error', function(er) {
  // an error occurred somewhere.
  // if we throw it now, it will crash the program
  // with the normal line number and stack message.
});

domain.enter()#

enter() メソッドは、run()bind()、そして intercept() メソッドを アクティブなドメインに結びつけるために使われます。 これは (このドメインオブジェクト、すなわち this を) domain.active および process.domain を設定し、ドメインモジュールによって管理される ドメインのスタックに暗黙的に積み上げます (ドメインのスタックに関する詳細は domain.exit() を参照)。

enter() の呼び出しはアクティブなドメインを変更することだけで、 ドメイン自身は変化しません。 enter()exit() は一つのドメインに対して何度でも呼び出すことができます。

もし enter() が呼び出されたドメインが破棄済みだと、 enter() は何も設定せずにリターンします。

domain.exit()#

exit() メソッドは現在のドメインから抜け出し、スタックから取り除きます。 非同期呼び出しのチェーンが異なるコンテキストに切り替わる場合はどんな時でも、 現在のドメインから確実に抜け出すことは重要です。 exit() の呼び出しは、ドメインに束縛された非同期呼び出しおよび I/O 操作のチェーンを、終端または途中で区切ります。

もし複数のネストしたドメインが現在の実行コンテキストに束縛されていると、 exit() はネストしたどのドメインからも抜け出します。

exit() の呼び出しはアクティブなドメインを変更することだけで、 ドメイン自身は変化しません。 enter()exit() は一つのドメインに対して何度でも呼び出すことができます。

もし exit() が呼び出されたドメインが破棄済みだと、 exit() は何も設定せずにリターンします。

domain.dispose()#

Stability: 0 - Deprecated.  IO 操作の明らかな失敗から回復するには、
ドメインに設定したエラーハンドラを通じておこなってください。

一度 dispose() が呼び出されると、run()bind()、または intercept() によってドメインに束縛されたコールバックはもうドメインが使われなくなります。 そして 'dispose' イベントが生成されます。