溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Nodejs中怎么對錯誤進行處理

發(fā)布時間:2021-07-21 09:56:56 來源:億速云 閱讀:132 作者:Leah 欄目:web開發(fā)

今天就跟大家聊聊有關Nodejs中怎么對錯誤進行處理,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。

假設我們有以下代碼

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

如果本機上沒有監(jiān)聽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的調(diào)用流程。

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++層直接調(diào)用Libuv的uv_tcp_connect,并且設置回調(diào)是AfterConnect。接著我們看libuv的實現(xiàn)。

do {       errno = 0;       // 非阻塞調(diào)用       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以異步的方式調(diào)用操作系統(tǒng),然后把request掛載到handle中,并且注冊等待可寫事件,當連接失敗的時候,就會執(zhí)行uv__stream_io回調(diào),我們看一下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);

獲取錯誤信息后回調(diào)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);

接著調(diào)用JS層的oncomplete回調(diào)。

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構(gòu)造錯誤信息,然后銷毀socket并且以ex為參數(shù)觸發(fā)error事件。我們看看uvExceptionWithHostPort的實現(xiàn)。

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;     }     // 獲取調(diào)用棧信息但不包括當前調(diào)用的函數(shù)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;    }

繼續(xù)往下看,uvErrmapGet調(diào)用了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"},   &hellip;&hellip;

所以導出到JS層的結(jié)果如下

{     // 鍵是一個數(shù)字,由Libuv定義,其實是封裝了操作系統(tǒng)的定義   UV_ECONNREFUSED: ["ECONNREFUSED", "connection refused"],       UV_ECONNRESET: ["ECONNRESET", "connection reset by peer"]      ...    }

Node.js最后會組裝這些信息返回給調(diào)用方。這就是我們輸出的錯誤信息。那么為什么會是ECONNREFUSED呢?我們看一下操作系統(tǒng)對于該錯誤碼的邏輯。

static void tcp_reset(struct sock *sk)   {       switch (sk->sk_state) {           case TCP_SYN_SENT:               sk->sk_err = ECONNREFUSED;               break;            // ...     }      }

當操作系統(tǒng)收到一個發(fā)給該socket的rst包的時候會執(zhí)行tcp_reset,我們看到當socket處于發(fā)送syn包等待ack的時候,如果收到一個fin包,則會設置錯誤碼為ECONNREFUSED。我們輸出的正是這個錯誤碼。

看完上述內(nèi)容,你們對Nodejs中怎么對錯誤進行處理有進一步的了解嗎?如果還想了解更多知識或者相關內(nèi)容,請關注億速云行業(yè)資訊頻道,感謝大家的支持。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI