鍍金池/ 教程/ HTML/
模塊
斷言測(cè)試
Buffer
Smalloc
TTY
概述
REPL
HTTP
DNS
路徑
集群
TLS/SSL
系統(tǒng)
加密
調(diào)試器
進(jìn)程
Punycode
虛擬機(jī)
HTTPS
網(wǎng)絡(luò)
Query String
C/C++ 插件
實(shí)用工具
文件系統(tǒng)
Zlib
子進(jìn)程
UDP/Datagram Sockets
定時(shí)器
逐行讀取
字符串解碼器
全局對(duì)象
事件
URL
控制臺(tái)

穩(wěn)定性: 2 - 不穩(wěn)定

域提供了一種方法,它能把多個(gè)不同的 IO 操作看成一個(gè)單獨(dú)組。如果任何一個(gè)注冊(cè)到域的事件或者回調(diào)觸發(fā) error 事件,或者拋出一個(gè)異常,域就會(huì)接收到通知,而不是在process.on('uncaughtException')處理函數(shù)一樣丟失錯(cuò)誤的上下文,也不會(huì)使程序立即退出。

警告:不要忽視錯(cuò)誤!

域錯(cuò)誤處理程序并不是一個(gè)錯(cuò)誤發(fā)生時(shí)關(guān)閉你的進(jìn)程的替代品。

基于 JavaScript 中拋出異常的工作原理,基本上不可能在不泄露引用,或者不造成一些其他未定義的狀態(tài)下,完全重現(xiàn)現(xiàn)場(chǎng)。

響應(yīng)拋出錯(cuò)誤最安全的方法就是關(guān)閉進(jìn)程。一個(gè)正常的服務(wù)器會(huì)可能有很多活躍的連接,因?yàn)槟硞€(gè)錯(cuò)誤就關(guān)閉所有連接顯然是不合理的。

比較好的方法是給觸發(fā)錯(cuò)誤的請(qǐng)求發(fā)送回應(yīng),讓其他連接正常工作時(shí),停止監(jiān)聽(tīng)觸發(fā)錯(cuò)誤的人的新請(qǐng)求。

按這種方法,和集群(cluster)模塊可以協(xié)同工作,當(dāng)某個(gè)進(jìn)程遇到錯(cuò)誤時(shí),主進(jìn)程可以復(fù)制一個(gè)新的進(jìn)程。對(duì)于 Node 程序,終端代理或者注冊(cè)的服務(wù),可以留意錯(cuò)誤并做出反應(yīng)。

舉例來(lái)說(shuō),下面的代碼就不是好辦法:

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

通過(guò)使用域的上下文,并將程序切為多個(gè)工作進(jìn)程,我們能夠更合理的響應(yīng),處理錯(cuò)誤更安全。

javascript
// 好一些的做法!

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

錯(cuò)誤對(duì)象的附加內(nèi)容

任何時(shí)候一個(gè)錯(cuò)誤被路由傳到一個(gè)域的時(shí),會(huì)添加幾個(gè)字段。

  • error.domain 第一個(gè)處理錯(cuò)誤的域
  • error.domainEmitter 用這個(gè)錯(cuò)誤對(duì)象觸發(fā) 'error' 事件的事件分發(fā)器
  • error.domainBound 綁定到 domain 的回調(diào)函數(shù),第一個(gè)參數(shù)是 error。
  • error.domainThrown boolean 值,表明是拋出錯(cuò)誤,分發(fā),或者傳遞給綁定的回到函數(shù)。

隱式綁定

如果域正在使用中,所有新分發(fā)的對(duì)象(包括 流對(duì)象,請(qǐng)求,響應(yīng)等)將會(huì)隱式的綁定到這個(gè)域。

另外,傳遞給底層事件循環(huán)(比如 fs.open 或其他接收回調(diào)的方法)的回調(diào)函數(shù)將會(huì)自動(dòng)的綁定到這個(gè)域。如果他們拋出異常,域會(huì)捕捉到錯(cuò)誤信息。

為了避免過(guò)度使用內(nèi)存,域?qū)ο蟛粫?huì)象隱式的添加為有效域的子對(duì)象。如果這樣做的話(huà),很容易影響到請(qǐng)求和響應(yīng)對(duì)象的垃圾回收。

如果你想將域?qū)ο笞鳛樽訉?duì)象嵌入到父域里,就必須顯式的添加它們。

隱式綁定路由拋出的錯(cuò)誤和 'error' 事件,但是不會(huì)注冊(cè)事件分發(fā)器到域,所以 domain.dispose() 不會(huì)關(guān)閉事件分發(fā)器。隱式綁定僅需注意拋出的錯(cuò)誤和 'error' 事件。

顯式綁定

有時(shí)候正在使用的域并不是某個(gè)事件分發(fā)器的域?;蛘哒f(shuō),事件分發(fā)器可能在某個(gè)域里創(chuàng)建,但是被綁定到另外一個(gè)域里。

例如,HTTP 服務(wù)器使用正一個(gè)域?qū)ο螅覀兿M梢悦恳粋€(gè)請(qǐng)求使用一個(gè)不同的域。

這可以通過(guò)顯式綁定來(lái)實(shí)現(xiàn)。

例如:

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

返回一個(gè)新的域?qū)ο蟆?/p>

Class: Domain

這個(gè)類(lèi)封裝了將錯(cuò)誤和沒(méi)有捕捉到的異常到有效對(duì)象功能。

域是 EventEmitter 的子類(lèi). 監(jiān)聽(tīng)它的 error事件來(lái)處理捕捉到的錯(cuò)誤。

domain.run(fn)

  • fn {Function}

在域的上下文運(yùn)行提供的函數(shù),隱式的綁定了所有的事件分發(fā)器,計(jì)時(shí)器和底層請(qǐng)求。

這是使用域的基本方法。

例如:

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

這個(gè)例子里程序不會(huì)崩潰,而會(huì)觸發(fā)d.on('error')。

domain.members

  • {Array}

顯式添加到域里的計(jì)時(shí)器和事件分發(fā)器數(shù)組。

domain.add(emitter)

  • emitter {EventEmitter | Timer} 添加到域里的計(jì)時(shí)器和事件分發(fā)器

顯式將一個(gè)分發(fā)器添加到域。如果分發(fā)器調(diào)用的事件處理函數(shù)拋出錯(cuò)誤,或者分發(fā)器遇到 error 事件,將會(huì)導(dǎo)向域的 error 事件,和隱式綁定一樣。

對(duì)于 setIntervalsetTimeout 返回的計(jì)時(shí)器同樣適用。如果這些回調(diào)函數(shù)拋出錯(cuò)誤,將會(huì)被域的 'error' 處理器捕捉到。

如果計(jì)時(shí)器或分發(fā)器已經(jīng)綁定到域,那它將會(huì)從上一個(gè)域移除,綁定到當(dāng)前域。

domain.remove(emitter)

  • emitter {EventEmitter | Timer} 要移除的分發(fā)器或計(jì)時(shí)器

與 domain.add(emitter) 函數(shù)恰恰相反,這個(gè)函數(shù)將分發(fā)器移除出域。

domain.bind(callback)

  • callback {Function} 回調(diào)函數(shù)
  • return: {Function}被綁定的函數(shù)

返回的函數(shù)是一個(gè)對(duì)于所提供的回調(diào)函數(shù)的包裝函數(shù)。當(dāng)調(diào)用這個(gè)返回的函數(shù)被時(shí),所有被拋出的錯(cuò)誤都會(huì)被導(dǎo)向到這個(gè)域的 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} 回調(diào)函數(shù)
  • return: {Function} 被攔截的函數(shù)

domain.bind(callback) 類(lèi)似。除了捕捉被拋出的錯(cuò)誤外,它還會(huì)攔截 Error 對(duì)象作為參數(shù)傳遞到這個(gè)函數(shù)。

這種方式下,常見(jiàn)的 if (er) return callback(er); 模式,能被一個(gè)地方一個(gè)錯(cuò)誤處理替換。

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()

這個(gè)函數(shù)就像 run, bind, 和 intercept 的管道系統(tǒng),它設(shè)置有效域。它設(shè)定了域的domain.active and process.domain,還隱式的將域推到域模塊管理的域棧(關(guān)于域棧的細(xì)節(jié)詳見(jiàn)domain.exit())。enter函數(shù)的調(diào)用,分隔了異步調(diào)用鏈以及綁定到一個(gè)域的I/O操作的結(jié)束或中斷。

調(diào)用enter僅改變活動(dòng)的域,而不改變域本身。在一個(gè)單獨(dú)的域里可以調(diào)用任意多次Enterexit

domain.exit()

exit 函數(shù)退出當(dāng)前域,并從域的棧里移除。每當(dāng)程序的執(zhí)行流程要切換到不同的異步調(diào)用鏈的時(shí)候,要保證退出當(dāng)前域。調(diào)用 exit 函數(shù),分隔了異步調(diào)用鏈,和綁定到一個(gè)域的I/O操作的結(jié)束或中斷。

如果有多個(gè)嵌套的域綁定到當(dāng)前的上下文,exit 函數(shù)將會(huì)退出所有嵌套。

調(diào)用 exit 僅改變活躍域,不會(huì)改變自身域。在一個(gè)單獨(dú)的域里可以調(diào)用任意多次Enterexit。

如果在這個(gè)域名下 exit 已經(jīng)被設(shè)置,exit 將不退出域返回。

domain.dispose()

穩(wěn)定性: 0 - 拋棄。通過(guò)域里設(shè)置的錯(cuò)誤事件來(lái)顯示的消除失敗的 IO 操作。

調(diào)用 dispos 后,通過(guò) run,bind 或 intercept 綁定到域的回調(diào)函數(shù)不再使用這個(gè)域,并且分發(fā) dispose 事件。

上一篇:下一篇:字符串解碼器