本文以连接错误ECONNREFUSED为例,看看nodejs对错误处理的过程。 假设我们有以下代码

1.  const net = require('net');  
2.  net.connect({port: 9999})

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

1.  events.js:170  
2.        throw er; // Unhandled 'error' event  
3.        ^  
4.    
5.  Error: connect ECONNREFUSED 127.0.0.1:9999  
6.      at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1088:14)  
7.  Emitted 'error' event at:  
8.      at emitErrorNT (internal/streams/destroy.js:91:8)  
9.      at emitErrorAndCloseNT (internal/streams/destroy.js:59:3)  
10.     at processTicksAndRejections (internal/process/task_queues.js:81:17)

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

1.  const req = new TCPConnectWrap();  
2.  req.oncomplete = afterConnect;  
3.  req.address = address;  
4.  req.port = port;  
5.  req.localAddress = localAddress;  
6.  req.localPort = localPort;  
7.  // 开始三次握手建立连接  
8.  err = self._handle.connect(req, address, port);

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

1.  err = req_wrap->Dispatch(uv_tcp_connect,  
2.                               &wrap->handle_,  
3.                               reinterpret_cast<const sockaddr*>(&addr),  
4.                               AfterConnect);

C++层直接调用Libuv的uv_tcp_connect,并且设置回调是AfterConnect。接着我们看libuv的实现。

1.  do {  
2.      errno = 0;  
3.      // 非阻塞调用  
4.      r = connect(uv__stream_fd(handle), addr, addrlen);  
5.    } while (r == -1 && errno == EINTR);  
6.    // 连接错误,判断错误码  
7.    if (r == -1 && errno != 0) {  
8.      // 还在连接中,不是错误,等待连接完成,事件变成可读  
9.      if (errno == EINPROGRESS)  
10.       ; /* not an error */  
11.     else if (errno == ECONNREFUSED)  
12.       // 连接被拒绝  
13.       handle->delayed_error = UV__ERR(ECONNREFUSED);  
14.     else  
15.       return UV__ERR(errno);  
16.   }  
17.   uv__req_init(handle->loop, req, UV_CONNECT);  
18.   req->cb = cb;  
19.   req->handle = (uv_stream_t*) handle;  
20.   QUEUE_INIT(&req->queue);  
21.   // 挂载到handle,等待可写事件  
22.   handle->connect_req = req;  
23.   uv__io_start(handle->loop, &handle->io_watcher, POLLOUT);

我们看到Libuv以异步的方式调用操作系统,然后把request挂载到handle中,并且注册等待可写事件,当连接失败的时候,就会执行uv stream_io回调,我们看一下Libuv的处理(uv stream_io)。

1.  getsockopt(uv__stream_fd(stream),  
2.                 SOL_SOCKET,  
3.                 SO_ERROR,  
4.                 &error,  
5.                 &errorsize);  
6.  error = UV__ERR(error);  
7.  if (req->cb)  
8.      req->cb(req, error);

获取错误信息后回调C++层的AfterConnect。

1.  Local<Value> argv[5] = {  
2.     Integer::New(env->isolate(), status),  
3.     wrap->object(),  
4.     req_wrap->object(),  
5.     Boolean::New(env->isolate(), readable),  
6.     Boolean::New(env->isolate(), writable)  
7.   };  
8.    
9.   req_wrap->MakeCallback(env->oncomplete_string(), arraysize(argv), argv);

接着调用JS层的oncomplete回调。

1.  const ex = exceptionWithHostPort(status,  
2.                                   'connect',  
3.                                   req.address,  
4.                                   req.port,  
5.                                   details);  
6.  if (details) {  
7.    ex.localAddress = req.localAddress;  
8.    ex.localPort = req.localPort;  
9.  }  
10. // 销毁socket  
11. self.destroy(ex);

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

1.  function uvExceptionWithHostPort(err, syscall, address, port) {  
2.    const [ code, uvmsg ] = uvErrmapGet(err) || uvUnmappedError;  
3.    const message = `${syscall} $[code]: ${uvmsg}`;  
4.    let details = '';  
5.    
6.    if (port && port > 0) {  
7.      details = ` ${address}:${port}`;  
8.    } else if (address) {  
9.      details = ` ${address}`;  
10.   }  
11.   const tmpLimit = Error.stackTraceLimit;  
12.   Error.stackTraceLimit = 0;  
13.   const ex = new Error(`${message}${details}`);  
14.   Error.stackTraceLimit = tmpLimit;  
15.   ex.code = code;  
16.   ex.errno = err;  
17.   ex.syscall = syscall;  
18.   ex.address = address;  
19.   if (port) {  
20.     ex.port = port;  
21.   }  
22.   // 获取调用栈信息但不包括当前调用的函数uvExceptionWithHostPort,注入stack字段到ex中  
23.   Error.captureStackTrace(ex, excludedStackFn || uvExceptionWithHostPort);  
24.   return ex;  
25. }

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

1.  function uvErrmapGet(name) {  
2.    uvBinding = lazyUv();  
3.    if (!uvBinding.errmap) {  
4.      uvBinding.errmap = uvBinding.getErrorMap();  
5.    }  
6.    return uvBinding.errmap.get(name);  
7.  }  
8.    
9.  function lazyUv() {  
10.   if (!uvBinding) {  
11.     uvBinding = internalBinding('uv');  
12.   }  
13.   return uvBinding;  
14. }

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

1.  void GetErrMap(const FunctionCallbackInfo<Value>& args) {  
2.    Environment* env = Environment::GetCurrent(args);  
3.    Isolate* isolate = env->isolate();  
4.    Local<Context> context = env->context();  
5.    
6.    Local<Map> err_map = Map::New(isolate);  
7.    // 从per_process::uv_errors_map中获取错误信息  
8.    size_t errors_len = arraysize(per_process::uv_errors_map);  
9.    // 赋值  
10.   for (size_t i = 0; i < errors_len; ++i) {  
11.      // map的键是 uv_errors_map每个元素中的value,值是name和message
12.     const auto& error = per_process::uv_errors_map[i];  
13.     Local<Value> arr[] = {OneByteString(isolate, error.name),  
14.                           OneByteString(isolate, error.message)}; 
15.     if (err_map  
16.             ->Set(context,  
17.                   Integer::New(isolate, error.value),  
18.                   Array::New(isolate, arr, arraysize(arr)))  
19.             .IsEmpty()) {  
20.       return;  
21.     }  
22.   }  
23.   
24.   args.GetReturnValue().Set(err_map);  
25. }

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

1.  struct UVError {
2.    int value;
3.    const char* name;
4.    const char* message;
5.  };
6.  
7.  static const struct UVError uv_errors_map[] = {  
8.  #define V(name, message) {UV_##name, #name, message},  
9.      UV_ERRNO_MAP(V)  
10. #undef V  
11. };

UV_ERRNO_MAP宏展开后如下

1.  {UV_E2BIG, "E2BIG", "argument list too long"},  
2.  {UV_EACCES, "EACCES", "permission denied"},  
3.  {UV_EADDRINUSE, "EADDRINUSE", "address already in use"},  
4.  ……

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

1.  {  
2.    // 键是一个数字,由Libuv定义,其实是封装了操作系统的定义
3.    UV_ECONNREFUSED: ["ECONNREFUSED", "connection refused"],    
4.    UV_ECONNRESET: ["ECONNRESET", "connection reset by peer"]   
5.    ...   
6.  }

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

1.  static void tcp_reset(struct sock *sk)  
2.  {  
3.      switch (sk->sk_state) {  
4.          case TCP_SYN_SENT:  
5.              sk->sk_err = ECONNREFUSED;  
6.              break;  
7.           // ...
8.      }  
9.    
10. }

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

总结

到此这篇关于nodejs的错误处理过程记录的文章就介绍到这了,更多相关nodejs错误处理内容请搜索程序员的世界以前的文章或继续浏览下面的相关文章希望大家以后多多支持程序员的世界!

nodejs的错误处理过程记录的更多相关文章

  1. Node.js 安全指南

    当项目周期快结束时,开发人员会越来越关注应用的“安全性”问题。一个安全的应用程序并不是一种奢侈,而是必要的。你应该在开发的每个阶段都考虑应用程序的安全性,例如系统架构、设计、编码,包括最后的部署。在这篇教程中,我们将一步步来学习如何提高Node.js应用程序安全性的方法。1. 数据验证 - 永远不要......

  2. nodejs事件和事件循环详解

    目录简介nodejs中的事件循环phase详解timerspending callbacksidle, preparepoll轮询checkclose callbackssetTimeout 和 setImmediate的区别两者的共同点unref 和 refprocess.nextTickproc......

  3. three.js cannon.js物理引擎之制作拥有物理特性的汽车

    今天郭先生说一说使用cannon.js的车辆辅助类让我们的汽车模型拥有物理特性。效果图如下,在线案例请点击博客原文。下面我们说一下今天要使用的两个类,并简单的看看他们的物理意义1. RaycastVehicle类这是车辆辅助类,将光线从车轮位置投射到地面并施加力。它决定车的位置,角度,质量等信息。下......

  4. Node8中AsyncHooks异步生命周期

    Async Hooks 是 Node8 新出来的特性,提供了一些 API 用于跟踪 NodeJs 中的异步资源的生命周期,属于 NodeJs 内置模块,可以直接引用。const async_hooks = require('async_hooks');这是一个很少使用的模块,为什么会有这个模块呢?我......

  5. TypeScript中 typeof ArrayInstance[number] 剖析

    假设这样一个场景,目前业务上仅对接了三方支付 'Alipay', 'Wxpay', 'PayPal', 实际业务 getPaymentMode 会根据不同支付方式进行不同的付款/结算流程。const PAYMENT_MODE = ['Alipay', 'Wxpay', 'PayPal'];func......

  6. JS实现公告上线滚动效果

    本文实例为大家分享了JS实现公告上线滚动效果的具体代码,供大家参考,具体内容如下实现的效果如下,新闻公告上下滚动。代码:Document* {padding: 0;margin: 0;box-sizing: border-box;}.notice-news {width: 400px;height:......

  7. three.js cannon.js物理引擎之Heightfield

    今天郭先生说一说cannon.js物理引擎之Heightfield高度场,学过场论的朋友都知道物理学中把某个物理量在空间的一个区域内的分布称为场,高度场就是与高度相关的场,而cannon.js物理引擎的Heightfield的高度就是关于两个变量的函数,可以表达为HEIGHT(i,j)。当然知不知道......

  8. postman接口自动化测试之利用node.js和xmysql连接、操作数据库

    一、背景使用postman进行接口自动化测试时,除了要验证接口的返回,有时候还要同时验证数据库的数据,或者将接口返回的数据与数据库的数据做对比,检验数据的正确性。有的时候还需要在执行自动化case之前,造一些测试数据,或者在跑完自动化之后,删除测试数据。所以,我们需要在postman里连接并操作数据......

  9. 在nodejs中创建child process

    目录简介child process异步创建进程同步创建进程在nodejs中创建child process简介nodejs的main event loop是单线程的,nodejs本身也维护着Worker Pool用来处理一些耗时的操作,我们还可以通过使用nodejs提供的worker_threads来......

  10. 一文秒懂nodejs中的异步编程

    文章目录 简介同步异步和阻塞非阻塞javascript中的回调回调函数的错误处理回调地狱 ES6中的Promise什么是PromisePromise的特点Promise的优点Promise的缺点Promise的用法Promise的执行顺序 async和awaitasync的执行顺序async的特点 ......

随机推荐

  1. [Python] Pandas 对数据进行查找、替换、筛选、排序、重复值和缺失值处理

    如何使用pandas模块中的函数对DataFrame中的数据进行查找和替换目录1. 数据文件2. 读数据3. 查找数据4. 替换数据4.1 一对一替换4.2 多对一替换4.3 多对多替换5. 插入数据6. 删除数据6.1 删除列6.2 删除行7. 处理缺失值7.1 数据准备7.2 查看缺失值7.3 ......

  2. Java关于继承、重写与重载、封装、接口的硬核干货

    Java语言在面向对象方面的知识点复杂繁琐,但是几乎是每个小伙伴学习编程必须踩的坑,其实,面向对象的底层都是一些计算机底层知识的结合,所以,不注重基础的程序猿,一定不是一个可以走的远的程序猿。那么,今天,我们先逐一地深入了解继承、重载、接口和构造器的知识。在学习这篇文章之前,如果有小伙伴对new过程......

  3. C#调用usb摄像头的实现方法

    1、下载AForge类库,下载地址:https://code.google.com/archive/p/aforge/downloads,我下载的版本是:AForge.NET Framework-2.2.5.exe;2、下载安装好后,将下载类库中的Release文件夹复制到C#项目的可执行文件文件夹......

  4. Oracle 常用命令大全(持续更新)

    数据库----数据库启动 & 关闭启动数据库SQL> startup nomount;SQL> alter database mount;SQL> alter database open; 关闭数据库SQL> shutdown immediate; 更多内容请参考:O......

  5. 关于Java注解(annotation)的简单理解

    一、什么是注解?从 JDK5 开始,Java增加对元数据的支持,也就是注解。简单理解就是代码里的特殊标志,这些标志可以在编译,类加载,运行时被读取,并执行相应的处理,以便于其他工具补充信息或者进行部署。二、为什么要使用注解?注解可以被其他程序(比如:编译器等)读取,开发人员可以在不改变原有代码和逻辑......

  6. 在PHP中灵活使用foreach+list处理多维数组的方法

    先抛出问题,有时候我们接收到的参数是多维数组,我们需要将他们转成普通的数组,比如: $arr = [ [1, 2, [3, 4]], [5, 6, [7, 8]],]; 我们需要的结果是元素1变成1,2,3,4,元素2变成5,6,7,8,这时候,我们就可以用foreach配合list来实......

  7. 基于源码分析Vue的nextTick

    摘要:本文通过结合官方文档、源码和其他文章整理后,对Vue的nextTick做深入解析。理解本文最好有浏览器事件循环的基础,建议先阅读上文《事件循环Event loop到底是什么》。一、官方定义实际上在弄清楚浏览器的事件循环后,Vue的nextTick就非常好理解了,它就是利用了事件循环的机制。我们......

  8. 备份和还原Windows DHCP服务器

    在本教程中,您将学习如何使用DHCP控制台和PowerShell备份和还原Windows DHCP服务器。您是否曾经经历过DHCP服务器崩溃或故障?在设备开始重新启动之前,一切都会平静。用户将抱怨他们无法访问电子邮件,互联网停止工作并且其应用程序不再工作。这是因为DHCP服务器已关闭,并且它们未连接......

  9. C# WPF实现的语音播放自定义控件

    原理很简单,利用Path画一个图,然后用动画进行播放,播放时间由依赖属性输入赋值与控件内部维护的一个计时器进行控制。控件基本是玩具,无法作为真实项目使用。因为没有设置播放源,所以编写异步播放源或者实际播放时候要将事件引发,是否播放等属性,事件移到真实播放事件非专业UI,即使知道怎么画图也是画的不如意......

  10. msxml3.dll 错误 800c0019 系统错误:-2146697191解决方法

    一个asp后台使用了XMLHTTP组件的页面无法无法生成静态页面了,运行时提示"msxml3.dll 错误 '800c0019′" 系统错误:-2146697191 请查找行 数。之前一直以为是服务器配置或数据库的原因,或者IE浏览器有问题,因为这是一个XMLHTTP对象组件,可......