首页 > 代码库 > Node.js v0.10.31API手册-Domain

Node.js v0.10.31API手册-Domain

Node.js v0.10.31API手册-目录


Domain(域)

Domains 提供了一种方式,即以一个单一的组的形式来处理多个不同的IO操作。如果任何一个注册到domain的事件触发器或回调触发了一个‘error’事件,或者抛出一个错误,那么domain对象将会被通知到。而不是直接让这个错误的上下文从`process.on(‘uncaughtException‘)‘处理程序中丢失掉,也不会致使程序因为这个错误伴随着错误码立即退出。

警告: 不要忽视错误!

Domain error处理程序不是一个在错误发生时,关闭你的进程的替代品。

基于‘抛出(throw)‘在JavaScript中工作的方式,几乎从来没有任何方式能够在‘不泄露引用,不造成一些其他种类的未定义的脆弱状态’的前提下,安全的“从你离开的地方重新拾起”。

响应一个被抛出错误的最安全方式就是关闭进程。当然,在一个正常的Web服务器中,你可能会有很多活跃的连接。由于其他触发的错误你去突然关闭这些连接是不合理。

更好的方法是发送错误响应给那个触发错误的请求,在保证其他人正常完成工作时,停止监听那个触发错误的人的新请求。

在这种方式中,使用伴随着集群模块,由于主过程可以叉新工人时,一个工人发生了一个错误。节点程序规模的多 机,终止代理或服务注册可以注意一下失败,并做出相应的反应。

举例来说,以下就不是一个好想法:

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


通过对域的上下文的使用,以及将我们的程序分隔成多个工作进程的反射,我们可以做出更加恰当的反应和更加安全的处理。


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

对Error(错误)对象的内容添加

每一次一个Error对象被导向经过一个域,它会添加几个新的字段。

  • error.domain 第一个处理这个错误的域。
  • error.domainEmitter 用这个错误对象触发‘error‘事件的事件分发器。
  • error.domainBound 回调函数,该回调函数被绑定到域,并且一个错误会作为第一参数传递给这个回调函数。
  • error.domainThrown 一个布尔值表明这个错误是否被抛出,分发或者传递给一个绑定的回调函数。

隐式绑定

如果多个域正在被使用,那么所有的EventEmitter对象(包括Stream对象,请求,应答等等)会被隐式绑定到它们被创建时的有效域。

另外,被传递到低层事件分发请求的回调函数(例如fs.open,或者其它接受回调函数的函数)会自动绑定到有效域。如果这些回调函数抛出错误,那么这个域会捕捉到这个错误。

为了防止内存的过度使用,Domain对象自己不会作为有效域的子对象被隐式添加到有效域。因为如果这样做的话,会很容易影响到请求和应答对象的正常垃圾回收。

如果你在一个父Domain对象里嵌套子Domain对象,那么你需要显式地添加它们。

隐式绑定将被抛出的错误和 ‘error‘事件导向到Domain对象的error事件,但不会注册到Domain对象上的EventEmitter对象,所以domain.dispose()不会令EventEmitter对象停止运作。隐式绑定只关心被抛出的错误和‘error‘事件。

显式绑定

有时,正在使用的域并不是某个事件分发器所应属的域。又或者,事件分发器在一个域内被创建,但是应该被绑定到另一个域。

例如,对于一个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
返回一个新的Domain对象。

类: Domain

Domain类封装了将错误和没有被捕捉的异常导向到有效对象的功能。

Domain是EventEmitter 类的一个子类。监听它的error事件来处理它捕捉到的错误。

domain.run(fn)

  • fn Function
在域的上下文里运行提供的函数,隐式地绑定所有该上下文里创建的事件分发器,计时器和低层请求。

这是使用一个域的最基本的方式。

示例:

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
一个元素是被显式添加到域里的计时器和事件分发器的数组

domain.add(emitter)

  • emitter EventEmitter | Timer 被添加到域里的时间分发器或计时器
显式地将一个分发器添加到域。如果这个分发器调用的任意一个事件处理器抛出一个错误,或是这个分发器分发了一个error 事,那么它会被导向到这个域的error 事件,就像隐式绑定所做的一样。

这对于从setInterval setTimeout返回的计时器同样适用。如果这些计时器的回调函数抛出错误,它将会被这个域的error 处理器捕捉到。

如果这个Timer或EventEmitter对象已经被绑定到另外一个域,那么它将会从那个域被移除,然后绑定到当前的域。

domain.remove(emitter)

  • emitter EventEmitter | Timer 要从域里被移除的分发器或计时器
domain.add(emitter)函数恰恰相反,这个函数将域处理从指明的分发器里移除。

domain.bind(callback)

  • callback Function 回调函数
  • return: Function 被绑定的函数
返回的函数会是一个对于所提供的回调函数的包装函数。当这个被返回的函数被调用时,所有被抛出的错误都会被导向到这个域的error 事件。

示例

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);的方式可以被一个单独地方的单独的错误处理所取代。

示例

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 函数对于runbindintercept 来说就像它们的管道系统:它们使用enter 函数来设置有效域。enter 函数对于域设定了domain.active process.domain,还隐式地将域推入了由域模块管理的域栈(关于域栈的细节详见domain.exit())。enter 函数的调用,分隔了异步调用链以及绑定到一个域的I/O操作的结束或中断。

调用enter 仅仅改变活动的域,而不改变域本身。 enter exit在一个单独的域可以被调用任意多次。

如果域的enter 已经设置,enter 将不设置域就返回。

domain.exit()

exit 函数退出当前的域,将当前域从域的栈里移除。每当当程序的执行流程准要切换到一个不同的异步调用链的上下文时,要保证退出当前的域。exit 函数的调用,分隔了异步调用链以及绑定到一个域的I/O操作的结束或中断。

如果有多个嵌套的域绑定到当前的执行上下文, 退出将退出在这个域里的所有的嵌套。

调用exit 只会改变有效域,而不会改变域自身。在一个单一域上,Enter exit 可以被调用任意次。

如果在这个域名下exit 已经被设置,exit 将不退出域返回。

domain.dispose()

dispose 方法销毁一个域的同时,会尽最大努力尝试清楚与该域相关联的的所有IO。流将被终止,结束,关闭或销毁。计时器将被清楚。显示绑定的回调函数将不再被调用。由该方法提出任何错误事件将被忽视

调用dispose 方法的目的通常是在域上下文的关键位置被发现错误时防止级联错误的。

一旦域被设置dispose 事件将被分发。

注意:IO操作仍旧可能被执行。然而最有可能的是,一旦一个域被设置来自分发器的进一步的错误将被忽略,所以,尽管一些剩余的动作依旧在执行,但Node.js将不会与这些动作进行进一步沟通。


Node.js v0.10.31API手册-Domain