内容简介: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为:
而我稍微变通一下,如果写入如下代码:
fs.open('xxx','r+',(err,data)=>{ if(err) console.log(new Error(err)) });
这个例子返回的err为:
可以看到,在这个里面把详细的发生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'})
我想大家也已经猜到了这个返回的大致内容的区别了吧:
第一个返回的错误栈非常详细,而第二个返回的仅仅是我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错误进行返回,这样可以以最快的速度定位到问题的原因出在哪里。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 使用事件总线共享组件之间的Props
- Android Binder,AIDL使用流程及进程之间的事件分发
- 黑白之间,烦请适当宽松
- 算法与运营之间的战争
- 组件之间的通讯LiveDataBus
- 工作与发财之间的秘密
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。