内容简介: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
- 工作与发财之间的秘密
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
应用Rails进行敏捷Web开发
Dave Thomas, David Hansson等 / 林芷薰 / 电子工业出版社 / 2006-7 / 65.00元
这是第一本关于Ruby on Rails的著作。 全书主要内容分为两大部分。在“构建应用程序”部分中,读者将看到一个完整的“在线购书网站” 示例。在演示的过程中,作者真实地再现了一个完整的迭代式开发过程,让读者亲身体验实际应用开发中遇到的各种问题、以及Rails如何有效解决这些问题。在随后的“Rails框架”部分中,作者深入介绍了Rails框架的各个组成部分。尤为值得一提的是本部分的后几章......一起来看看 《应用Rails进行敏捷Web开发》 这本书的介绍吧!