本文转载自微信公众号《编程杂技》,作者theanarkh。转载本文请联系编程杂技公众号。本文以连接错误ECONNREFUSED为例,看看nodejs是如何处理错误的。假设我们有如下代码constnet=require('net');net.connect({port:9999})如果本机没有9999监听端口,那么我们会得到如下输出。events.js:170thrower;//未处理的'error'事件^Error:connectECONNREFUSED127.0.0.1:9999atTCPConnectWrap.afterConnect[asoncomplete](net.js:1088:14)Emitted'error'eventat:atemitErrorNT(internal/streams/destroy.js:91:8)atemitErrorAndCloseNT(internal/streams/destroy.js:59:3)atprocessTicksAndRejections(internal/process/task_queues.js:81:17)简单看一下connect的调用过程。constreq=newTCPConnectWrap();req.oncomplete=afterConnect;req.address=address;req.port=port;req.localAddress=localAddress;req.localPort=localPort;//开始三次握手建立连接err=self._handle.connect(请求、地址、端口);然后再看C++层的逻辑connecterr=req_wrap->Dispatch(uv_tcp_connect,&wrap->handle_,reinterpret_cast(&addr),AfterConnect);C++层直接调用Libuv的uv_tcp_connect,设置回调为AfterConnect。然后我们看libuv的实现。do{errno=0;//非阻塞调用r=connect(uv__stream_fd(handle),addr,addrlen);}while(r==-1&&errno==EINTR);//连接错误,判断错误码if(r==-1&&errno!=0){//仍然连接,不是错误,等待连接完成,事件变为可读if(errno==EINPROGRESS);/*notanerror*/elseif(errno==ECONNREFUSED)//ConnectionRejectedhandle->delayed_error=UV__ERR(ECONNREFUSED);elsereturnUV__ERR(errno);}uv__req_init(handle->loop,req,UV_CONNECT);req->cb=cb;req->handle=(uv_stream_t*)handle;QUEUE_INIT(&req->queue);//挂载到句柄上,等待可写事件handle->connect_req=req;uv__io_start(handle->loop,&handle->io_watcher,POLLOUT);我们看到Libuv异步调用操作系统,然后将请求挂载到句柄上,并注册等待可写事件。当连接失败时,将执行uv__stream_io回调。我们来看看Libuv的处理(uv__stream_io)。getsockopt(uv__stream_fd(stream),SOL_SOCKET,SO_ERROR,&error,&errorsize);error=UV__ERR(error);if(req->cb)req->cb(req,error);得到错误信息后,回调C++层的AfterConnect。Localargv[5]={Integer::New(env->isolate(),status),wrap->object(),req_wrap->object(),Boolean::New(env->isolate(),readable),Boolean::New(env->isolate(),writable)};req_wrap->MakeCallback(env->oncomplete_string(),arraysize(argv),argv);然后调用JS层的oncomplete回调。constex=exceptionWithHostPort(status,'connect',req.address,req.port,details);if(details){ex.localAddress=req.localAddress;ex.localPort=req.localPort;}//销毁socketself.destroy(前任);exceptionWithHostPort构造一个错误信息,然后销毁套接字并触发一个以ex为参数的错误事件。我们来看一下uvExceptionWithHostPort的实现。functionuvExceptionWithHostPort(err,syscall,address,port){const[code,uvmsg]=uvErrmapGet(err)||uvUnmappedError;constmessage=`${syscall}${code}:${uvmsg}`;letdetails='';if(port&&port>0){details=`${address}:${port}`;}elseif(address){details=`${address}`;}consttmpLimit=Error.stackTraceLimit;Error.stackTraceLimit=0;constex=newError(`${message}${details}`);Error.stackTraceLimit=tmpLimit;ex.code=code;ex.errno=err;ex.syscall=syscall;ex.address=address;if(port){ex.port=port;}//获取调用栈信息但不包括当前调用的函数uvExceptionWithHostPort,将栈域注入exError.captureStackTrace(ex,excludedStackFn||uvExceptionWithHostPort);returnx;}我们主要通过uvErrmapGet获取错误信息;}functionlazyUv(){if(!uvBinding){uvBinding=internalBinding('uv');}returnuvBinding;}继续往下看,uvErrmapGet调用了C++层uv模块的getErrorMap。voidGetErrMap(constFunctionCallbackInfo&args){Environment*env=Environment::GetCurrent(args);Isolate*isolate=env->isolate();Localcontext=env->context();Local//从per_process::uv_errors_map=获取错误信息size_terrors_lenarraysize(per_process::uv_errors_map);//Assignmentfor(size_ti=0;i
