node中关于Event Emitter的error事件和new Error之间,你所不知道的事

栏目: C++ · 发布时间: 7年前

内容简介:node中关于Event Emitter的error事件和new Error之间,你所不知道的事

前言:很多人都讨论过promise的reject回调的错误没有详细错误栈的问题。其实我们在讨论这个问题的时候,忘记了promise出现的初衷--为了解决callback hell的问题,在浏览器端,promise主要为了解决ajax的问题,而在服务端,promise主要解决的便是Event Emitter的回调问题。而promise打印出的错误日志,便是Event Emitter中传递出来的error事件。

从两类不同的Error实例说起

我要说的第一类error便是 Event Emitter 中抛出的错误,在这里做一个简单的实验( 本文中所有实例均基于node v8.0构建 ):

//内置的event Emitter 的 error
fs.open('xxx','r+',(err,data)=>{
if(err){
console.log(err)
}
})

这个例子返回的err为:

node中关于Event Emitter的error事件和new Error之间,你所不知道的事

而我稍微变通一下,如果写入如下代码:

fs.open('xxx','r+',(err,data)=>{
if(err)
console.log(new Error(err))
});

这个例子返回的err为:

node中关于Event Emitter的error事件和new Error之间,你所不知道的事

可以看到,在这个里面把详细的发生error的位置打印出来了,方便开发人员进行定位。

如果大家感觉这个例子不够直观的话,那么接下来看直接调用Event Emitter的例子:

const events = require('events').EventEmitter;
class Emit extends events{

}
var emits = new Emit();
emits.on('error',e=>console.log(e));
emits.emit('error',new Error('test'))
emits.emit('error',{Error:'error'})

我想大家也已经猜到了这个返回的大致内容的区别了吧:

node中关于Event Emitter的error事件和new Error之间,你所不知道的事

第一个返回的错误栈非常详细,而第二个返回的仅仅是我emit的那个对象。以上几个例子的区别在于,一个调用了new Error来抛出错误,而另一种则是直接抛出Event Emitter的回调。

深入剖析Event Emitter中的错误处理方式

大家应该都知道,Event Emitter是基于libuv的,而http、fs、stream等很多模块都是基于Event Emitter的,正如第一节的例子,我们就以fs.open为切入点,来看一下底层libuv对错误的处理。

libuv中包括文件和网络两个常用的模块(其实还有高级轮询等,这里不做详细介绍),因为上面的例子用的是fs.open,那么我们fs.open为主线,来贯穿整套逻辑。首先便是 src/unix/fs.c ,这里面有libuv中所有fs相关方法的定义。

int uv_fs_open(uv_loop_t* loop,
               uv_fs_t* req,
               const char* path,
               int flags,
               int mode,
               uv_fs_cb cb) {
  INIT(OPEN);
  PATH;
  req->flags = flags;
  req->mode = mode;
  POST;
}

在这里简单介绍uv_fs_open各个参数或者类型的含义:uv_loop_t 便是一个event_loop了,一般来说用的都是uv_default_loop,uv_fs_t这个参数很重要,它里面包含了reslut,flag以及mode等域,接下来就是path了,这个意思大家应该都很明了,就是读取文件的路径,flags和mode就不多说了,就是标准的Unix Flags,uv_fs_cb就是callback了。

下面我们将视线转移到node的架构中 src/node_file.cc

static void Open(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);

  int len = args.Length();
  if (len < 1)
    return TYPE_ERROR("path required");
  if (len < 2)
    return TYPE_ERROR("flags required");
  if (len < 3)
    return TYPE_ERROR("mode required");
  if (!args[1]->IsInt32())
    return TYPE_ERROR("flags must be an int");
  if (!args[2]->IsInt32())
    return TYPE_ERROR("mode must be an int");

  BufferValue path(env->isolate(), args[0]);
  ASSERT_PATH(path)

  int flags = args[1]->Int32Value();
  int mode = static_cast<int>(args[2]->Int32Value());

  if (args[3]->IsObject()) {
    ASYNC_CALL(open, args[3], UTF8, *path, flags, mode)
  } else {
    SYNC_CALL(open, *path, *path, flags, mode)
    args.GetReturnValue().Set(SYNC_RESULT);
  }
}

我们可以发现,真正的调用在ASYNC_CALL,这里可能读者好奇了,我们只有三个参数啊,为何会走到args[3]的判断中,一会下面到js的源码中再跟大家剖析一下。一步一步进行溯源,发现node端真正对uv_fs_open进行调用的地方在上面的宏定义中:

#define ASYNC_DEST_CALL(func, request, dest, encoding, ...)                   \
  Environment* env = Environment::GetCurrent(args);                           \
  CHECK(request->IsObject());                                                 \
  FSReqWrap* req_wrap = FSReqWrap::New(env, request.As<Object>(),             \
                                       #func, dest, encoding);                \
  int err = uv_fs_ ## func(env->event_loop(),                                 \
                           req_wrap->req(),                                   \
                           __VA_ARGS__,                                       \
                           After);                                            \
  req_wrap->Dispatched();                                                     \
  if (err < 0) {                                                              \
    uv_fs_t* uv_req = req_wrap->req();                                        \
    uv_req->result = err;                                                     \
    uv_req->path = nullptr;                                                   \
    After(uv_req);                                                            \
    req_wrap = nullptr;                                                       \
  } else {                                                                    \
    args.GetReturnValue().Set(req_wrap->persistent());                        \
  }

在这里读者需要注意下uv_fs_##func实现了对uv_fs_open 真正的调用,因为开始的代码为了触发错误,所以req->result 必小于0,即为进入到err<0的逻辑中,在这里,又对After进行了调用,注意After中有一段代码:

if (req->result < 0) {
    // An error happened.
    argv[0] = UVException(env->isolate(),
                          req->result,
                          req_wrap->syscall(),
                          nullptr,
                          req->path,
                          req_wrap->data());
  } else {
 //...
 }

在这里,对错误捕捉用的是UVException,而没有用throwUVException,主要作用在于这个错误是系统错误,并非js的问题,监听error事件完全可以监听到,没有必要throw出来,而在UVException的定义中,会把整体错误进行整合并变成object抛给error事件的回调:

Local<Object> e = Exception::Error(js_msg)->ToObject(isolate);

  e->Set(env->errno_string(), Integer::New(isolate, errorno));
  e->Set(env->code_string(), js_code);
  e->Set(env->syscall_string(), js_syscall);
  if (!js_path.IsEmpty())
    e->Set(env->path_string(), js_path);
  if (!js_dest.IsEmpty())
    e->Set(env->dest_string(), js_dest);

node中对fs此类错误的处理

扒完了c++部分的流程,接下来node部分的就比较简单了,视线移到 /lib/fs.js ,这是require('fs')的fs文件,其中比较瞩目的便是:

const constants = process.binding('constants').fs;
const fs = exports;
Object.defineProperty(exports, 'constants', {
  configurable: false,
  enumerable: true,
  value: constants
});

可以发现,fs中,js调用c++的方法通过的是process.binding,所以说process对象中封装的都是底层c++的方法,其中fs.open的源码也是相当简单:

fs.open = function(path, flags, mode, callback_) {
  var callback = makeCallback(arguments[arguments.length - 1]);
  mode = modeNum(mode, 0o666);

  if (handleError((path = getPathFromURL(path)), callback))
    return;
  if (!nullCheck(path, callback)) return;

  var req = new FSReqWrap();
  req.oncomplete = callback;

  binding.open(pathModule._makeLong(path),
               stringToFlags(flags),
               mode,
               req);
};

在这里,mode参数被处理了,如果没有参数的话则会返回0o666,所以说,我们刚才会走到ASYNC_CALL这个逻辑中。重点主要放到makeCallback这个函数身上:

function makeCallback(cb) {
  if (cb === undefined) {
    return rethrow();
  }

  if (typeof cb !== 'function') {
    throw new TypeError('"callback" argument must be a function');
  }

  return function() {
    return cb.apply(null, arguments);
  };
}

这里返回的是一个函数,并没有对cb进行调用,而是apply了一下arguments,接下来我们移步到刚才的函数,发现在handleError中进行了callback的调用:

function handleError(val, callback) {
  if (val instanceof Error) {
    if (typeof callback === 'function') {
      process.nextTick(callback, val);
      return true;
    } else throw val;
  }
  return false;
}

这里有一个非常重要的地方 val instanseof Error ,说明 libuv生成的error也是Error的实例 ,通过这一圈源码观光,我们可以发现一个很有趣的地方,开始我们测试的两个都为Error的实例,但是为什么内容不一样呢?这里引申出来了一个概念,就是error分为两种:javascript错误和系统错误,而fs.open 属于第二种---系统错误。系统错误error实例的stack方法返回的是libuv中定义好的错误的返回,而不是javascript错误中关于stack的返回,所以说凡是涉及到Event Emitter的错误,我们都需要做一层new Error,强制转换成javascript错误进行返回,这样可以以最快的速度定位到问题的原因出在哪里。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

无线:网络文化中激进的经验主义

无线:网络文化中激进的经验主义

[英] 阿德里安·麦肯齐 / 张帆 / 上海译文出版社 / 2018-9

本书研究了无线是如何成为当代人类经验的主角的。从路由器、智能电话、电子书、城市到在线工作、服务协议、玩具以及国家等各个方面,人们已经感觉到了无线技术所引发的变革。本书作者援引一个世纪之前的哲学技术来分析当代最前沿的后网络时代的人类状况。基于威廉•詹姆斯的实用主义哲学相关的彻底经验主义,作者提出了把失序的无线网络世界与人们的感知匹配起来的新方式。一起来看看 《无线:网络文化中激进的经验主义》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

html转js在线工具
html转js在线工具

html转js在线工具