Понимание Try/Catch и доменов для обработки ошибок в узле
Я исследовал правильный способ обработки ошибок в Node и нашел несколько хороших ответов здесь, в Stackru и на сайте NodeJS, таких как Как предотвратить сбой node.js? try-catch не работает, а сами документы NodeJS: http://nodejs.org/api/domain.html.
Тем не менее, у меня осталось пара вопросов относительно того, когда и где использовать try/catch и / или домены. Я понимаю, что это связано с асинхронным и синхронным кодом, но даже внутри примера кода, представленного на сайте NodeJS, касающемся доменов, они используют try/catch внутри обработчика ошибок домена. Может ли кто-нибудь объяснить это более подробно, не будет ли попытка / отлов перехватывать асинхронные ошибки внутри обработчика ошибок?
Кроме того, документы NodeJS предполагают, что вы все равно должны завершить процесс для исключения, и поэтому код из документов домена предлагает использовать кластер для разветвления нового дочернего процесса / работника при обнаружении исключения. Основная причина:
По самой природе того, как throw работает в JavaScript, почти никогда не существует способа безопасно "взять то, что вы остановили", без утечки ссылок или создания какого-либо другого неопределенного хрупкого состояния.
Может кто-нибудь объяснить это? Какова природа работы throw в Javascript? Почему ресурсы просачиваются как сумасшедшие? И всегда ли необходимо перезапускать процесс или убивать / запускать работника?
Например, я внедрял ORM JugglingDB и однажды забыл запустить мой локальный сервер MySQL. Я столкнулся с ECONNREFUSED
ошибка, которая привела к сбою процесса. Понимая, что это может произойти в производственной среде (сбой БД или временно недоступен), я хотел поймать эту ошибку и обработать ее изящно; Повторите попытку подключения, сохраните переменную состояния для БД и, возможно, обработайте запросы, ответив временно недоступным сообщением. Try / Catch просто не уловил ошибку, и хотя я вижу, что могу использовать домен, используя рекомендованную стратегию, я буду в бесконечном цикле убийств и запуска работников, пока БД не вернется в рабочее состояние.
JugglingDB, по какой-либо причине, имеет только "подключенное" событие, но не имеет какой-либо функции обратного вызова, которая передает объект ошибки; он пытается соединиться в тот момент, когда вы создаете экземпляр класса, и выдает ошибки, которые не перехватываются и изящно выводятся. Это заставило меня хотеть посмотреть на другие ORM, но это все еще не отвечает на мои вопросы относительно того, как справиться с такой ситуацией. Было бы неправильно использовать домен, чтобы перехватывать потенциальные ошибки соединения и корректно обрабатывать его, не запуская новый процесс?
Вот вопрос / проблема, которую я разместил на github JugglingDB: https://github.com/1602/jugglingdb/issues/405, а вот трассировка стека от ошибки, возникшей в JugglingDB, когда сервер не существует (только происходит, когда включена опция объединения):
Error: connect ECONNREFUSED
at errnoException (net.js:901:11)
at Object.afterConnect [as oncomplete] (net.js:892:19)
--------------------
at Protocol._enqueue (/Users/aaronstorck/Sites/site/node_modules/jugglingdb-mysql/node_modules/mysql/lib/protocol/Protocol.js:110:48)
at Protocol.handshake (/Users/aaronstorck/Sites/site/node_modules/jugglingdb-mysql/node_modules/mysql/lib/protocol/Protocol.js:42:41)
at PoolConnection.Connection.connect (/Users/aaronstorck/Sites/site/node_modules/jugglingdb-mysql/node_modules/mysql/lib/Connection.js:101:18)
at Pool.getConnection (/Users/aaronstorck/Sites/site/node_modules/jugglingdb-mysql/node_modules/mysql/lib/Pool.js:42:23)
at Pool.query (/Users/aaronstorck/Sites/site/node_modules/jugglingdb-mysql/node_modules/mysql/lib/Pool.js:185:8)
at initDatabase (/Users/aaronstorck/Sites/site/node_modules/jugglingdb-mysql/lib/mysql.js:62:20)
at initializeConnection (/Users/aaronstorck/Sites/site/node_modules/jugglingdb-mysql/lib/mysql.js:49:9)
at Object.initializeSchema [as initialize] (/Users/aaronstorck/Sites/site/node_modules/jugglingdb-mysql/lib/mysql.js:33:5)
at new Schema (/Users/aaronstorck/Sites/site/node_modules/jugglingdb/lib/schema.js:105:13)
at Application.loadConnections (/Users/aaronstorck/Sites/site/core/application.js:95:40)
Process finished with exit code 8
Заранее спасибо за любую часть этого, вы можете помочь мне понять!:)
1 ответ
Глядя на JugglingDB и пытаясь подключиться к несуществующему серверу MySQL на моем ноутбуке, я получаю ECONNREFUSED
, но он регистрируется только каждые несколько секунд (пытается восстановить соединение) и не приводит к сбою моего процесса. Я понижаю свой jugglingdb-mysql до 0.0.6, затем могу повторить то, что вы говорите. И я посмотрел на источник jugglingdb-mysql и обнаружил, что если ошибка соединения обнаружена, она просто выдана. Это плохо и не очень хорошее поведение. Хотя журналы спама тоже плохи, но выбрасывать ошибки, которые можно обрабатывать, хуже. Поэтому я предлагаю обновить до 0.0.7 или 0.0.8(самое последнее), чтобы вам не пришлось беспокоиться об этой ошибке. Я просто делаю отчет об ошибке для JugglingDB или jugglingdb-mysql для правильного распространения ошибок, как это делает node-mysql. node-mysql - очень хороший пример того, как это делается.
Теперь давайте перейдем к тому, как обрабатывать ошибки в node.js. Не все ошибки выбрасываются в node.js. Например, вы можете сделать это:
require('fs').readFile('non-existent-file-yes-gimmie-a-error', function (error) { })
Обратный вызов будет вызван с ошибкой, но он не будет выдан. Если вы разработчик модуля, то в подобных случаях всегда распространяйте ошибку. Либо вызвать обратный вызов с ошибкой, либо вызвать обработчик ошибок или эмитировать error
событие на вашем событии эмитента. старая версия jugglingdb-mysql - очень плохой пример того, как следует обрабатывать ошибки. Если вы не можете поймать ошибку с try catch
тогда ты не должен бросать это. Бросать только тогда, когда он может быть перехвачен, как функции ядра библиотеки node.js. Если вы делаете require('fs').readFile()
он немедленно выдаст ошибку, которую можно отследить. Но в случаях, когда ошибка найдена, после того, как функция вернулась (при обработке асинхронного события), она вызовет обратный вызов с ошибкой.
Теперь я уверен, что вы столкнулись с большим количеством сбоев, формируете неуловимые ошибки. Скорее всего, они от источника событий error
События. В node.js, когда error
событие генерируется, если нет обработчиков, оно генерируется. Поэтому, если вы хотите отлавливать ошибки от создателей событий, просто добавьте error
событие. Примером этого является fs.createReadStream
который вернет источник события:
require('fs').createReadStream('non-exitent-file-gimmie-a-error')
Это, безусловно, приведет к сбою процесса, но если вы можете обработать ошибку (например, передайте 404 запросу http, вызвавшему это), добавьте error
обработчик, и он больше не выдаст ошибку:
require('fs').createReadStream('non-exitent-file-gimmie-a-error').on('error', handleError)
Помимо ошибок ввода / вывода, существуют ошибки типов и ссылок. Это те, с которыми вы должны быть осторожны.
Например, у вас может быть что-то вроде этого (чего у вас не будет, но только для образовательных целей):
var routes = {
'/' : function () {...},
'/one' : function () {...},
'/two' : function () {...}
}
require('http').createServer(function (req, res) {
fs.open('layout.html', 'r', function (err, fd) {
if (err) return errorHandler(err);
var buffer = new Buffer(4000); // Lets say we know our file is 4000 bytes exatly
fs.read(fd, buffer, 0, 4000, function (err, data) {
if (err) return errorHandler(err);
try {
routes[req.url](req, res, data);
fs.close(fd);
} catch (e) {
errorHandler(err);
}
});
});
function errorHandler(err) {
res.writeHead(404);
res.end();
}
}).listen(1337)
Теперь, как вы видите, если routes[req.url]
не существует, будет выдано сообщение об ошибке, потому что мы пытаемся вызвать errorHandler, но файл остается открытым, и мы забыли закрыть его при ошибке. Если сделано 10000 запросов с неправильным URL-адресом, то у вас исчерпан лимит вашего процесса. Вы можете исправить это, но положить fs.close(fd)
в последнем предложении вместо.
Давайте представим это, но без try
а также catch
, но ловит ошибку с глобальным доменом. В некоторых случаях вы больше не будете знать о состоянии вашей программы, поэтому в случае ошибки вы не можете просто разрешить приложению продолжиться, потому что в таких случаях это приведет к утечке файловых дескрипторов.
Эта проблема применяется везде, где нам приходится писать код для очистки, и, как разработчики, мы не всегда думаем обо всех полученных нами материалах, и мы всегда будем делать ошибки. Вот почему предлагается завершить процесс. Вы можете поймать ошибку с process.on('uncaughtException')
или же domain.on('error')
но вы должны убить процесс после того, как вы сделали свою уборку.
Но вы должны быть осторожны с этим. Попробуйте потерпеть крах, если не знаете, откуда возникла ошибка. Если вы знаете, откуда он (как в предыдущем случае, у нас открыт только один файл), то очистите его, его ресурсы и пусть ваш процесс продолжится, потому что злоумышленник может узнать, как завершить ваш процесс с помощью злонамеренного ввода и ДОС это.
В случаях, когда вы решаете сделать что-то, а затем вылетаете, убедитесь, что вы установили тайм-аут, а затем сбой процесса, когда тайм-аут случается. unref
тайм-аут на узле v0.10, чтобы убедиться, что процесс не будет поддерживаться, а на v0.8 вы можете использовать такой модуль, как addTimeout, который очистит тайм-аут при вызове обратного вызова.