2016-08-19 13 views
5

xem xét sau mã javascript thực hiện trong nodejs:NodeJS: http.ClientRequest bắn kiện lỗi hai lần trong kịch bản cụ thể

// 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(); 

Dự kiến ​​sản lượng:

{ 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 

đầu ra thực tế:

{ 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 

Như bạn có thể thấy, http.ClientRequest (hoặc có thể stream.Writable?) Kích hoạt sự kiện lỗi hai lần, trước tiên với ECONNREFUSED và phía sau er ngoại lệ bị bắt, ECONNRESET.

Điều này không xảy ra nếu chúng tôi thực thi gọi lại không đồng bộ trong trình xử lý lỗi http.ClientRequest bằng cách sử dụng nextTick hoặc setTimeout, ví dụ: thay đổi này mang lại hành vi mong đợi:

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

Bất cứ ai có thể giải thích tại sao điều này xảy ra và nếu đây là lỗi hoặc hoạt động như mong đợi? Hành vi giống nhau trong nút 4.x mới nhất và nút 6.x.

Cảm ơn!

Trả lời

3

Bản tóm tắt

Vấn đề là bạn đang ném một lỗi không xác định trong cuộc gọi lại người nghe. Node cố tình không xử lý các ngoại lệ không mong muốn do đó hậu quả là mã thường chạy sau khi lỗi này bị bỏ qua vì điều khiển phải đi đến bắt thích hợp (trong trường hợp này là cách xử lý ngoại lệ chưa bắt buộc của bạn). những điều không xảy ra, nhưng đáng chú ý nhất là HTTPRequest không nhận thức được rằng nó đã phát ra thành công lỗi khi nó được gọi trở lại để đóng socket.

Các chi tiết và tài liệu tham khảo

triết lý chung Node là not to trap unexpected throws và nút xử lý mô hình này như một programmer error rằng nên cho phép để đạt được một sự thất bại. (Tài liệu không rõ ràng về API cho gọi lại của người nghe sự kiện, nhưng cũng không cung cấp ví dụ trong đó một trường hợp ngoại lệ hoặc hướng dẫn để xem xét khả năng khi xử lý bộ phát ở phía nhà phát triển của API luồng.)

Khi nào ngoại lệ của bạn lan truyền đến người phát, và người nghe tiếp theo và phần còn lại của việc dọn dẹp và đánh dấu của ổ cắm không xảy ra, gây ra ClientRequest để nghĩ rằng nó cần phải cung cấp lỗi một lần nữa:

emitter. theo mã để chặn lỗi thứ 2:

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; 

Kể từ khi ném của bạn đã không bắt được ở đó, Check cho biến này sau đó tìm thấy _hadError vẫn unset:

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; 
} 

Nếu bạn đẩy lỗi của bạn ném vào một khối không đồng bộ sau đó bạn không chặn phần còn lại của ổ cắm quá trình dọn dẹp để tiếp tục như Ngoại lệ sẽ xảy ra trong một số chồng chức năng khác.

Trong một số trường hợp khác, nút cẩn thận khi gọi cuộc gọi lại và những gì được đặt trước đó.Nhưng điều này phần lớn được thực hiện để cho phép callbacks để làm một số ngẫu nhiên thay thế, vv, như đã thấy trong comment này:

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 

Trao đổi thứ tự của thiết _hadError và phát ra sẽ ngăn chặn lỗi thứ hai này cho bạn và có vẻ an toàn cho rằng đây dường như chỉ là séc _hadError. Nhưng:

  • nếu điều này được sử dụng để ngăn chặn các lỗi khác trong tương lai thì đó sẽ tác động tiêu cực callbacks lỗi mà cố gắng để thăm dò tình trạng kết nối trong một lỗi

  • nó sẽ vẫn được giữ ổ cắm trong trạng thái dọn dẹp một phần không tốt cho các chương trình sống lâu hơn.

Vì vậy, tôi thường nói tốt hơn là không trực tiếp ném ngoại lệ trong các cuộc gọi lại trừ khi bạn có tình huống bất thường trong đó việc xử lý nội bộ bình thường phải được ngăn chặn hoặc ghi đè.

Các vấn đề liên quan