Skip to content

Latest commit

 

History

History
315 lines (229 loc) · 10.9 KB

File metadata and controls

315 lines (229 loc) · 10.9 KB

十七、domain和 Express.js

domain模块是一个核心 Node.js 模块(http://nodejs.org/api/domain.html),它通过使用,帮助开发人员跟踪和隔离错误,这通常是一项艰巨的任务。把域想象成一个更聪明的版本try / catch语句 1

定义问题

为了说明异步代码可能给错误处理和调试带来的障碍,请看这段同步代码,它显示了“Custom Error: Fail!”正如我们所料:

try {
  throw new Error('Fail!');
} catch (e) {
  console.log('Custom Error: ' + e.message);
}

现在,让我们以setTimeout()函数的形式添加异步代码,这将从 10 到 100(来自文件ch17/async-errors.js)随机延迟执行毫秒数:

try {
setTimeout(function () {
    throw new Error('Fail!');
  }, Math.round(Math.random()*100));
} catch (e) {
  console.log('Custom Error: ' + e.message);
}

而不是“自定义错误:失败!”消息,我们看到一个标准的、未处理的错误(“错误:失败!”)可能是这样的:

/Users/azat/Documents/Code/proexpressjs/ch17/async-errors.js:4
      throw new Error("Fail!");
            ^
Error: Fail!
    at null._onTimeout (/Users/azat/Documents/Code/proexpressjs/ch17/async-errors.js:4:13)
    at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)

因此,try / catch在异步错误时失败。一个好的经验法则是仅对同步 JavaScript/Node.js 代码使用try / catch

Image 提示 try / catch也可能会慢一些,尤其是在内部定义函数的时候,因为 JavaScript 引擎无法提前优化代码。关于try / catch含义的更多信息,请参见https://github.com/joyent/node/wiki/Best-practices-and-gotchas-with-v8http://jsperf.com/try-catch-performance-overhead

探索一个基本的领域示例

在我们深入研究domain和 Express.js 之前,让我们先来探索一下基本的域示例(ch17/basic.js)。在其中,我们实例化了域对象:

var domain = require('domain').create();

然后,我们附加一个事件监听器,它告诉我们如何处理这个错误:

domain.on('error', function(error){
  console.log(error);
});

大部分代码和容易出错的代码被回调到domain.run() :

domain.run(function(){
  throw new Error('Failed!');
});

现在,让我们通过用域(ch17/basic-timeout.js)替换try / catch来修改setTimeout()示例:

var domain = require('domain');
var d = domain.create();
d.on('error', function(e) {
   console.log('Custom Error: ' + e);
});
d.run(function() {
  setTimeout(function () {
    throw new Error('Failed!');
  }, Math.round(Math.random()*100));
});

当我们用$ node basic-timeout.js运行它时,输出是

Custom Error: Error: Failed!

编写领域应用

了解了领域的基础知识后,让我们来探索一下ch17/domain中的应用。它有两条路线,/ error-domain/ error-no-domain 。从路线的名称,你可以猜测他们都将崩溃。域路由(/error-domain)将发回一些 JSON,而非域路由(/error-no-domain)将使用来自errorhandler模块的标准错误处理中间件。

这是域驱动的容易出错的路线:

app.get('/error-domain', function (req, res, next) {
  var d = domain.create();
  d.on('error', function (error) {
    console.error(error.stack);
    d.exit()
    res.status(500).send({'Custom Error': error.message});
  });
  d.run(function () {
    // Error prone code goes here
    throw new Error('Database is down.');
  });
});

我们不必在每条路由中都使用域;例如,/error-no-domainerrorhandler模块的中间件使用标准的 Express.js 方法:

app.get('/error-no-domain', function (req, res, next) {
  // Error prone code goes here
  next(new Error('Database is down.'));
});

我们添加了一些定制逻辑,通过使用domain.active ( http://nodejs.org/api/domain.html#domain_domain_enter)来区分域和非域案例(路由):

app.use(function (error, req, res, next) {
  console.log(domain)
  if (domain.active) {
    console.info('Caught with domain', domain.active);
    domain.active.emit('error', error);
  } else {
    console.info('No domain');
    defaultHandler(error, req, res, next);
  }
});

当你用$ node app.js运行应用并转到http://localhost:3000/error-domainhttp://localhost:3000/error-no-domain时,你会分别看到 JSON 页面和 HTML 页面。在服务器日志上,你会看到/error-domain的“带域捕获”和/error-no-domain的“无域”。

请注意,在这两种情况下,Express.js 应用都优雅地处理了错误,并且没有导致崩溃。到目前为止,您可能想知道,如果标准的 Express.js 可以工作,为什么还要在 domain 上经历这么多麻烦。上例中的路由在“表面”级别抛出错误,即同步错误。然而,大多数事情都发生在异步代码中。还记得本章前面的setTimeout例子吗?它可以模拟数据库调用,因此让我们通过在两条路由中添加异步错误来使我们的示例更加真实:

app.get('/error-domain', function (req, res, next) {
  var d = domain.create();
  d.on('error', function (error) {
    console.error(error.stack);
    d.exit()
    res.status(500).send({'Custom Error': error.message});
  });
  d.run(function () {
    // Error-prone code goes here
    // throw new Error('Database is down.');
    setTimeout(function () {
      throw new Error('Database is down.');
    }, Math.round(Math.random()*100));
  });
});

app.get('/error-no-domain', function (req, res, next) {
  // Error-prone code goes here
  // throw new Error('Database is down.');
  setTimeout(function () {
    throw new Error('Database is down.');
  }, Math.round(Math.random()*100));
});

现在,在浏览器中检查路线。非域路由会惨失败停服务器!该消息可能如下所示:

/Users/azat/Documents/Code/proexpressjs/ch17/domain/app.js:41
    throw new Error('Database is down.');
          ^
Error: Database is down.
    at null._onTimeout (/Users/azat/Documents/Code/proexpressjs/ch17/domain/app.js:41:11)
    at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)

与非域路由不同,启用域的路由不会使服务器崩溃。相反,它发回了我们的自定义错误 JSON。因此,使用域名的好处!

工作(或者“崩溃”更准确)的例子在 GitHub 的ch17/domain文件夹中。 2

以下是ch17/domain/app.js的全部内容:

var http = require('http'),
  express = require('express'),
  path = require('path'),
  logger = require('morgan'),
  favicon = require('serve-favicon'),
  errorHandler = require('errorhandler');

var app = express();

app.set('port', process.env.PORT || 3000);
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.use(logger('combined'));
app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(express.static(path.join(__dirname, 'public')));

var domain = require('domain');
var defaultHandler = errorHandler();

app.get('/error-domain', function (req, res, next) {
  var d = domain.create();
  d.on('error', function (error) {
    console.error(error.stack);
    d.exit()
    res.status(500).send({'Custom Error': error.message});
  });
  d.run(function () {
    // Error prone code goes here
    // throw new Error('Database is down.');
    setTimeout(function () {
      throw new Error('Database is down.');
    }, Math.round(Math.random()*100));
  });
});

app.get('/error-no-domain', function (req, res, next) {
  // Error prone code goes here
  // throw new Error('Database is down.');
  setTimeout(function () {
    throw new Error('Database is down.');
  }, Math.round(Math.random()*100));
});

app.use(function (error, req, res, next) {
  console.log(domain)
  if (domain.active) {
    console.info('Caught with domain', domain.active);
    domain.active.emit('error', error);
  } else {
    console.info('No domain');
    defaultHandler(error, req, res, next);
  }
});

var server = app.listen(app.get('port'), function() {
  console.log('Express server listening on port ' + server.address().port);
});

通过在中间件中实现域,您可以用最少的代码重复将域方法应用于每一个路由。确保将这个中间件放在路由之前。

和 Express.js 一样,有一个第三方模块将每条路由封装在域中:express-domain-middleware ( https://www.npmjs.org/package/express-domain-middleware)。

您可以通过以下方式安装express-domain-middleware v0.1.0:

$ npm install express-domain-middleware@0.1.0

然后,用require()将模块导入到您的项目中:

var expressDomain = require('express-domain-middleware');

将此中间件应用到您的 Express.js 应用,在其他中间件和路由之前使用app.use() :

app.use(expressDomain);

好好享受!

另一个与 domain 和 Express.js 一起用于错误处理的好模块是okay ( https://www.npmjs.org/package/ok)。这里的想法是okay是手动错误检查的更有力的替代,例如:

if (error) return next(error);

与前面的行不同,前面的行在每个嵌套的回调中重复出现(如果您写得正确,您应该在每个回调中处理错误),您需要做的只是用两个参数调用okay:一个错误回调(例如next)和一个无错误回调(例如常规闭包)。在某种程度上,okay是函数和常规回调之间的额外一层。

有了okay,代码变得更加清晰,正如您在这个 Express.js 路径中看到的:

var okay = require('okay');
app.get('/', function(req, res, next) {
   fs.readFile('file.txt', 'utf8', okay(next, function(contents) {
      res.send(contents);
   });
});

Image 注意在撰写本文时(2014 年),Domain 模块正处于不稳定的阶段。这意味着它的方法和行为可能会改变。因此,保持更新并使用package.json文件中的精确版本。

摘要

对于大多数程序员来说,使用域可能是一个难以理解的概念。如果你是其中之一,不要绝望!这是因为我们必须处理异步代码问题。我们需要改变我们的整个心态。出于这个原因,本章从一些非常基本的异步代码例子开始,来说明这些挑战。然后,我们讨论了 domain 和 Express.js 的使用。说到 Express.js,其受欢迎程度和 Node.js 的 NPM 的一个辉煌之处在于,它们是几乎任何东西的中间件。因此,如果理解域对您来说不是一个优先的 Node.js 主题,您可以只应用中间件,然后忘掉它。

使用 Domain 模块是使用框架的最后一个 Express.js 技巧和窍门。在下一章中,我们将返回到“Hello World”模式,以了解为什么开始学习 Express.js 的 Node.js 堆栈很重要,即使您计划使用不同的框架(剧透一下:这是因为许多 Node.js 框架借鉴了 Express.js 的一些概念或依赖于 express . js)。


1T0】

2T0】