2016-08-19 85 views
5

考慮以下在執行的NodeJS JavaScript代碼:的NodeJS:http.ClientRequest在特定情況下觸發錯誤事件兩次

// create ClientRequest 
// port 55555 is not opened 
var req = require('http').request('http://localhost:55555', function() { 
    console.log('should be never reached'); 
}); 

function cb() { 
    throw new Error(); 
} 

req.on('error', function(e) { 
console.log(e); 
cb(); 
}); 

// exceptions handler 
process.on('uncaughtException', function() { 
console.log('exception caught. doing some async clean-up before exit...'); 
setTimeout(function() { 
    console.log('exiting'); 
    process.exit(1); 
}, 2000); 
}); 

// send request 
req.end(); 

預期輸出:

{ Error: connect ECONNREFUSED 127.0.0.1:55555 
    at Object.exports._errnoException (util.js:1026:11) 
    at exports._exceptionWithHostPort (util.js:1049:20) 
    at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1081:14) 
    code: 'ECONNREFUSED', 
    errno: 'ECONNREFUSED', 
    syscall: 'connect', 
    address: '127.0.0.1', 
    port: 55555 } 
exception caught. doing some async clean-up before exit... 
exiting 

實際輸出:

{ Error: connect ECONNREFUSED 127.0.0.1:55555 
    at Object.exports._errnoException (util.js:1026:11) 
    at exports._exceptionWithHostPort (util.js:1049:20) 
    at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1081:14) 
    code: 'ECONNREFUSED', 
    errno: 'ECONNREFUSED', 
    syscall: 'connect', 
    address: '127.0.0.1', 
    port: 55555 } 
exception caught. doing some async clean-up before exit... 
{ Error: socket hang up 
    at createHangUpError (_http_client.js:252:15) 
    at Socket.socketCloseListener (_http_client.js:284:23) 
    at emitOne (events.js:101:20) 
    at Socket.emit (events.js:188:7) 
    at TCP._handle.close [as _onclose] (net.js:492:12) code: 'ECONNRESET' } 
exception caught. doing some async clean-up before exit... 
exiting 

正如你所看到的,http.ClientRequest(或者可能是stream.Writable?)兩次觸發錯誤事件,首先使用ECONNREFUSED和aft呃異常被捕獲,ECONNRESET。

如果我們在http.ClientRequest錯誤處理程序中使用nextTick或setTimeout例如異步執行回調,則不會發生這種情況。這一變化給了預期的行爲:

req.on('error', function(e) { 
console.log(e); 
process.nextTick(cb); 
}); 

誰能解釋爲什麼發生這種情況,如果這是一個錯誤或按預期工作?行爲在最新的節點4.x和節點6.x中是相同的。

謝謝!

回答

3

簡介

麻煩的是,你的聽衆回調裏面拋出未被捕獲的錯誤。節點故意不處理意外的異常,因此結果是在該錯誤後正常運行的代碼已被跳過,因爲控制必須轉到相應的catch(在這種情況下,一直到未捕獲的異常處理)。這會導致一些事情不會發生,但最顯而易見的是,HTTPRequest不知道它在成功關閉套接字時被調用時發出錯誤。

詳細信息和參考

節點的總體理念是not to trap unexpected throws和節點對待這種模式爲programmer error應該被允許到達故障。 (對於事件偵聽器回調,文檔沒有明確說明API,但也沒有提供任何示例,其中一個拋出異常或在流API的開發者端處理髮射器時考慮可能性的說明。)

當您的異常傳播到發射器,並且隨後的偵聽器和其餘的清除和插座標記不會發生,導致ClientRequest認爲它需要再次提供錯誤:

您的回調拋出的emitter被遵循通過代碼來抑制第二個錯誤:

req.emit('error', err); 
// For Safety. Some additional errors might fire later on 
// and we need to make sure we don't double-fire the error event. 
req.socket._hadError = true; 

因爲你扔的是沒有抓到那裏,Check這個變量,然後找到_hadError還是取消設置:

if (!req.res && !req.socket._hadError) { 
    // If we don't have a response then we know that the socket 
    // ended prematurely and we need to emit an error on the request. 
    req.emit('error', createHangUpError()); 
    req.socket._hadError = true; 
} 

如果你把你的錯誤扔到另一個異步塊,那麼你不會阻止插座的休息清理過程將繼續,因爲異常將發生在其他一些功能堆棧中。

在其他一些情況下,節點在調用回調函數和事先設置的函數時很小心。但這很大程度上是完成以允許回調做一些替代的清理等,如在此comment

we set destroyed to true before firing error callbacks in order 
to make it re-entrance safe in case Socket.prototype.destroy() 
is called within callbacks 

交換設置_hadError和發射會抑制第二個錯誤您的訂單和給定的,這似乎是安全似乎是唯一的_hadError檢查。但是:

  • 如果這是用來在未來抑制更多的錯誤,那麼這將產生負面的錯誤回調試圖錯誤

  • 它仍然會留在探測連接的狀態影響插座處於部分清理狀態,對於較長時間的生活程序來說並不好。

所以我一般會說最好不要直接扔在回調例外,除非你有一個不尋常的情況下正常的內部處理必須被阻止或覆蓋。