web前端开发框架node.js学习之Nodejs的错误处理
小职 2021-04-25 来源 :编程杂技 阅读 983 评论 0

摘要:本文主要介绍了web前端开发框架node.js学习之Nodejs的错误处理,通过具体的内容向大家展现,希望对大家web前端框架node.js开发的学习有所帮助。

本文主要介绍了web前端开发框架node.js学习之Nodejs的错误处理,通过具体的内容向大家展现,希望对大家web前端框架node.js开发的学习有所帮助。

web前端开发框架node.js学习之Nodejs的错误处理


以连接错误ECONNREFUSED为例,看看nodejs对错误处理的过程。

 

假设我们有以下代码

 

const net = require('net');

 

net.connect({port: 9999})

如果本机上没有监听9999端口,那么我们会得到以下输出。

 

events.js:170   

       throw er; // Unhandled 'error' event   

       ^   

    

 Error: connect ECONNREFUSED 127.0.0.1:9999   

     at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1088:14)   

 Emitted 'error' event at:   

     at emitErrorNT (internal/streams/destroy.js:91:8)   

     at emitErrorAndCloseNT (internal/streams/destroy.js:59:3)   

     at processTicksAndRejections (internal/process/task_queues.js:81:17)  

我们简单看一下connect的调用流程。

 

const req = new TCPConnectWrap();   

req.oncomplete = afterConnect;   

req.address = address;   

req.port = port;   

req.localAddress = localAddress;   

req.localPort = localPort;   

// 开始三次握手建立连接   

err = self._handle.connect(req, address, port);  

接着我们看一下C++层connect的逻辑

 

err = 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)   

      ; /* not an error */   

    else if (errno == ECONNREFUSED)   

      // 连接被拒绝   

      handle->delayed_error = UV__ERR(ECONNREFUSED);   

    else   

      return UV__ERR(errno);   

  }   

  uv__req_init(handle->loop, req, UV_CONNECT);   

  req->cb = cb;   

  req->handle = (uv_stream_t*) handle;   

  QUEUE_INIT(&req->queue);   

  // 挂载到handle,等待可写事件   

  handle->connect_req = req;   

uv__io_start(handle->loop, &handle->io_watcher, POLLOUT);

我们看到Libuv以异步的方式调用操作系统,然后把request挂载到handle中,并且注册等待可写事件,当连接失败的时候,就会执行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回调。

 

const ex = exceptionWithHostPort(status,   

                                 'connect',   

                                 req.address,   

                                 req.port,   

                                 details);   

if (details) {   

  ex.localAddress = req.localAddress;   

  ex.localPort = req.localPort;   

}   

// 销毁socket   

self.destroy(ex);

exceptionWithHostPort构造错误信息,然后销毁socket并且以ex为参数触发error事件。我们看看uvExceptionWithHostPort的实现。

 

function uvExceptionWithHostPort(err, syscall, address, port) {   

  const [ code, uvmsg ] = uvErrmapGet(err) || uvUnmappedError;   

  const message = `${syscall} ${code}: ${uvmsg}`;   

  let details = '';   

   

  if (port && port > 0) {   

    details = ` ${address}:${port}`;   

  } else if (address) {   

    details = ` ${address}`;   

  }   

  const tmpLimit = Error.stackTraceLimit;   

  Error.stackTraceLimit = 0;   

  const ex = new Error(`${message}${details}`);   

  Error.stackTraceLimit = tmpLimit;   

  ex.code = code;   

  ex.errno = err;   

  ex.syscall = syscall;   

  ex.address = address;   

  if (port) {   

    ex.port = port;   

  }   

  // 获取调用栈信息但不包括当前调用的函数uvExceptionWithHostPort,注入stack字段到ex中   

  Error.captureStackTrace(ex, excludedStackFn || uvExceptionWithHostPort);   

  return ex;   

}   

我们看到错误信息主要通过uvErrmapGet获取

 

unction uvErrmapGet(name) {   

   uvBinding = lazyUv();   

   if (!uvBinding.errmap) {   

     uvBinding.errmap = uvBinding.getErrorMap();   

   }   

   return uvBinding.errmap.get(name);   

 }   

    

 function lazyUv() {   

   if (!uvBinding) {   

     uvBinding = internalBinding('uv');   

   }   

   return uvBinding;   

 }

继续往下看,uvErrmapGet调用了C++层的uv模块的getErrorMap。

 

void GetErrMap(const FunctionCallbackInfo& args) {   

  Environment* env = Environment::GetCurrent(args);   

  Isolate* isolate = env->isolate();   

  Localcontext = env->context();   

   

  Local

  // 从per_process::uv_errors_map中获取错误信息   

  size_t errors_len = arraysize(per_process::uv_errors_map);   

  // 赋值   

  for (size_t i = 0; i < errors_len; ++i) {   

     // map的键是 uv_errors_map每个元素中的value,值是name和message

    const auto& error = per_process::uv_errors_map[i];   

    Localarr[] = {OneByteString(isolate, error.name),   

                          OneByteString(isolate, error.message)};  

    if (err_map   

            ->Set(context,   

                  Integer::New(isolate, error.value),   

                  Array::New(isolate, arr, arraysize(arr)))   

            .IsEmpty()) {   

      return;   

    }   

  }   

   

  args.GetReturnValue().Set(err_map);   

}

我们看到错误信息存在per_process::uv_errors_map中,我们看一下uv_errors_map的定义。

 

struct UVError {

  int value;

  const char* name;

  const char* message;

};

 

static const struct UVError uv_errors_map[] = {   

#define V(name, message) {UV_##name, #name, message},   

    UV_ERRNO_MAP(V)   

#undef V   

};   

UV_ERRNO_MAP宏展开后如下

 

{UV_E2BIG, "E2BIG", "argument list too long"},   

{UV_EACCES, "EACCES", "permission denied"},   

{UV_EADDRINUSE, "EADDRINUSE", "address already in use"},   

……  

所以导出到JS层的结果如下

 

{   

  // 键是一个数字,由Libuv定义,其实是封装了操作系统的定义

  UV_ECONNREFUSED: ["ECONNREFUSED", "connection refused"],     

  UV_ECONNRESET: ["ECONNRESET", "connection reset by peer"]    

  ...    

}  

Node.js最后会组装这些信息返回给调用方。这就是我们输出的错误信息。那么为什么会是ECONNREFUSED呢?我们看一下操作系统对于该错误码的逻辑。

 

static void tcp_reset(struct sock *sk)   

{   

    switch (sk->sk_state) {   

        case TCP_SYN_SENT:   

            sk->sk_err = ECONNREFUSED;   

            break;   

         // ...

    }   

   

}  

当操作系统收到一个发给该socket的rst包的时候会执行tcp_reset,我们看到当socket处于发送syn包等待ack的时候,如果收到一个fin包,则会设置错误码为ECONNREFUSED。我们输出的正是这个错误码。



我是小职,记得找我

✅ 解锁高薪工作

✅ 免费获取基础课程·答疑解惑·职业测评

web前端开发框架node.js学习之Nodejs的错误处理

本文由 @小职 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程