解析Node.js异常处理中domain模块的使用方法


Posted in Javascript onFebruary 16, 2016

NodeJS 提供了 domain 模块,可以简化异步代码的异常处理。在介绍该模块之前,我们需要首先理解“域”的概念。简单的讲,一个域就是一个 JS 运行环境,在一个运行环境中,如果一个异常没有被捕获,将作为一个全局异常被抛出。NodeJS 通过 process 对象提供了捕获全局异常的方法,示例代码如下

process.on('uncaughtException', function (err) {
  console.log('Error: %s', err.message);
});

setTimeout(function (fn) {
  fn();
});
Error: undefined is not a function

虽然全局异常有个地方可以捕获了,但是对于大多数异常,我们希望尽早捕获,并根据结果决定代码的执行路径。我们用以下 HTTP 服务器代码作为例子:

function async(request, callback) {
  // Do something.
  asyncA(request, function (err, data) {
    if (err) {
      callback(err);
    } else {
      // Do something
      asyncB(request, function (err, data) {
        if (err) {
          callback(err);
        } else {
          // Do something
          asyncC(request, function (err, data) {
            if (err) {
              callback(err);
            } else {
              // Do something
              callback(null, data);
            }
          });
        }
      });
    }
  });
}

http.createServer(function (request, response) {
  async(request, function (err, data) {
    if (err) {
      response.writeHead(500);
      response.end();
    } else {
      response.writeHead(200);
      response.end(data);
    }
  });
});

以上代码将请求对象交给异步函数处理后,再根据处理结果返回响应。这里采用了使用回调函数传递异常的方案,因此 async 函数内部如果再多几个异步函数调用的话,代码就变成上边这副鬼样子了。为了让代码好看点,我们可以在每处理一个请求时,使用 domain 模块创建一个子域(JS 子运行环境)。在子域内运行的代码可以随意抛出异常,而这些异常可以通过子域对象的 error 事件统一捕获。于是以上代码可以做如下改造:

function async(request, callback) {
  // Do something.
  asyncA(request, function (data) {
    // Do something
    asyncB(request, function (data) {
      // Do something
      asyncC(request, function (data) {
        // Do something
        callback(data);
      });
    });
  });
}

http.createServer(function (request, response) {
  var d = domain.create();

  d.on('error', function () {
    response.writeHead(500);
    response.end();
  });

  d.run(function () {
    async(request, function (data) {
      response.writeHead(200);
      response.end(data);
    });
  });
});

可以看到,我们使用.create方法创建了一个子域对象,并通过.run方法进入需要在子域中运行的代码的入口点。而位于子域中的异步函数回调函数由于不再需要捕获异常,代码一下子瘦身很多。

陷阱
无论是通过 process 对象的 uncaughtException 事件捕获到全局异常,还是通过子域对象的 error 事件捕获到了子域异常,在 NodeJS 官方文档里都强烈建议处理完异常后立即重启程序,而不是让程序继续运行。按照官方文档的说法,发生异常后的程序处于一个不确定的运行状态,如果不立即退出的话,程序可能会发生严重内存泄漏,也可能表现得很奇怪。

但这里需要澄清一些事实。JS 本身的throw..try..catch异常处理机制并不会导致内存泄漏,也不会让程序的执行结果出乎意料,但 NodeJS 并不是存粹的 JS。NodeJS 里大量的 API 内部是用 C/C++ 实现的,因此 NodeJS 程序的运行过程中,代码执行路径穿梭于 JS 引擎内部和外部,而 JS 的异常抛出机制可能会打断正常的代码执行流程,导致 C/C++ 部分的代码表现异常,进而导致内存泄漏等问题。

因此,使用 uncaughtException 或 domain 捕获异常,代码执行路径里涉及到了 C/C++ 部分的代码时,如果不能确定是否会导致内存泄漏等问题,最好在处理完异常后重启程序比较妥当。而使用 try 语句捕获异常时一般捕获到的都是 JS 本身的异常,不用担心上诉问题。

Javascript 相关文章推荐
js实现addClass,removeClass,hasClass的函数代码
Jul 13 Javascript
JS/Jquery判断对象为空的方法
Jun 11 Javascript
js实现图片放大和拖拽特效代码分享
Sep 05 Javascript
Bootstrap组件学习之导航、标签、面包屑导航(精品)
May 17 Javascript
sso跨域写cookie的一段js脚本(推荐)
May 25 Javascript
AngularJs  Creating Services详解及示例代码
Sep 02 Javascript
Javascript之深入浅出prototype
Feb 06 Javascript
详解闭包解决jQuery中AJAX的外部变量问题
Feb 22 Javascript
jquery 手势密码插件
Mar 17 Javascript
Bootstrap Multiselect 常用组件实现代码
Jul 09 Javascript
JavaScript实现简单音乐播放器
Apr 17 Javascript
JS使用JSON.parse(),JSON.stringify()实现对对象的深拷贝功能分析
Mar 06 Javascript
jQuery Timelinr实现垂直水平时间轴插件(附源码下载)
Feb 16 #Javascript
深入浅析AngularJS和DataModel
Feb 16 #Javascript
Javascript中的Prototype到底是什么
Feb 16 #Javascript
剖析Node.js异步编程中的回调与代码设计模式
Feb 16 #Javascript
使用Node.js处理前端代码文件的编码问题
Feb 16 #Javascript
让图片跳跃起来  javascript图片轮播特效
Feb 16 #Javascript
Node.js本地文件操作之文件拷贝与目录遍历的方法
Feb 16 #Javascript
You might like
PHP中Session的概念
2006/10/09 PHP
php curl的深入解析
2013/06/02 PHP
PHP使用CURL实现对带有验证码的网站进行模拟登录的方法
2014/07/23 PHP
浅析php工厂模式
2014/11/25 PHP
php图片水印添加、压缩、剪切的封装类实现
2020/04/18 PHP
利用PHP自动生成印有用户信息的名片
2016/08/01 PHP
详细解读php的命名空间(二)
2018/02/21 PHP
javascript的对话框详解与参数
2007/03/08 Javascript
解析ScrollPic在ie8下只滚动一遍,然后变为空白 ie6,ie7,chrome,firefox正常
2013/06/26 Javascript
超链接的禁用属性Disabled使用示例
2014/07/31 Javascript
javascript移动开发中touch触摸事件详解
2016/03/18 Javascript
AngularJS过滤器filter用法实例分析
2016/11/04 Javascript
详解PHP后期静态绑定分析与应用
2018/03/21 Javascript
React+Webpack快速上手指南(小结)
2018/08/15 Javascript
ES6 系列之 Generator 的自动执行的方法示例
2018/10/19 Javascript
使用node搭建自动发图文微博机器人的方法
2019/03/22 Javascript
vue 输入电话号码自动按3-4-4分割功能的实现代码
2020/04/30 Javascript
[01:02:10]DOTA2上海特级锦标赛B组小组赛#2 VG VS Fnatic第一局
2016/02/26 DOTA
[54:10]完美世界DOTA2联赛PWL S2 Magma vs FTD 第二场 11.29
2020/12/03 DOTA
Python2中的raw_input() 与 input()
2015/06/12 Python
在Python的Django框架中包装视图函数
2015/07/20 Python
python3实现ftp服务功能(服务端 For Linux)
2017/03/24 Python
Python标准模块--ContextManager上下文管理器的具体用法
2017/11/27 Python
Python读取图片为16进制表示简单代码
2018/01/19 Python
用Python将结果保存为xlsx的方法
2019/01/28 Python
python tkinter实现屏保程序
2019/07/30 Python
浅谈django url请求与数据库连接池的共享问题
2019/08/29 Python
Python基于模块Paramiko实现SSHv2协议
2020/04/28 Python
HTML5 画布canvas使用方法
2016/03/18 HTML / CSS
Skyscanner阿联酋:全球领先的旅游搜索平台
2017/11/25 全球购物
Skyscanner台湾:全球知名的旅行比价引擎
2018/07/01 全球购物
Linux管理员面试题 Linux admin interview questions
2014/11/01 面试题
初始化了一个没有run()方法的线程类,是否会出错?
2014/03/27 面试题
《鸟岛》教学反思
2014/04/26 职场文书
将Python代码打包成.exe可执行文件的完整步骤
2021/05/12 Python
tomcat默认最大连接数及相关调整方法
2022/05/06 Servers