Node.js Обработка исключений при передовой практике

Я только что начал опробовать node.js несколько дней назад. Я понял, что Node завершается всякий раз, когда у меня есть необработанное исключение в моей программе. Это отличается от обычного контейнера сервера, на котором я был обнаружен, где только рабочий поток умирает, когда происходят необработанные исключения, и контейнер все равно сможет получить запрос. Это вызывает несколько вопросов:

  • Является ли process.on('uncaughtException') единственным эффективным способом защиты от него?
  • Будет ли process.on('uncaughtException') улавливать необработанное исключение при выполнении асинхронных процессов?
  • Есть ли уже построенный модуль (например, отправка электронной почты или запись в файл), который я мог бы использовать в случае неперехваченных исключений?

Я был бы признателен за любой указатель/статью, которая показала бы мне общие рекомендации по обработке исключенных исключений в node.js

Ответ 1

Обновление: Joyent теперь имеет свой собственный гид, упомянутый в этом ответе. Следующая информация представляет собой более подробную информацию:

Безопасные "метательные" ошибки

В идеале мы хотели бы избежать как можно большего количества ошибок, а не буквально бросать ошибку, вместо этого мы можем безопасно "выбросить" ошибку, используя один из следующих методов, в зависимости от нашей архитектуры кода:

  • Для синхронного кода, если произошла ошибка, верните ошибку:

    // Define divider as a syncrhonous function
    var divideSync = function(x,y) {
        // if error condition?
        if ( y === 0 ) {
            // "throw" the error safely by returning it
            return new Error("Can't divide by zero")
        }
        else {
            // no error occured, continue on
            return x/y
        }
    }
    
    // Divide 4/2
    var result = divideSync(4,2)
    // did an error occur?
    if ( result instanceof Error ) {
        // handle the error safely
        console.log('4/2=err', result)
    }
    else {
        // no error occured, continue on
        console.log('4/2='+result)
    }
    
    // Divide 4/0
    result = divideSync(4,0)
    // did an error occur?
    if ( result instanceof Error ) {
        // handle the error safely
        console.log('4/0=err', result)
    }
    else {
        // no error occured, continue on
        console.log('4/0='+result)
    }
    
  • Для кода с обратным вызовом (то есть асинхронного) первым аргументом обратного вызова является err, если произошла ошибка err - ошибка, если ошибка не возникает, тогда err null. Любые другие аргументы следуют аргументу err:

    var divide = function(x,y,next) {
        // if error condition?
        if ( y === 0 ) {
            // "throw" the error safely by calling the completion callback
            // with the first argument being the error
            next(new Error("Can't divide by zero"))
        }
        else {
            // no error occured, continue on
            next(null, x/y)
        }
    }
    
    divide(4,2,function(err,result){
        // did an error occur?
        if ( err ) {
            // handle the error safely
            console.log('4/2=err', err)
        }
        else {
            // no error occured, continue on
            console.log('4/2='+result)
        }
    })
    
    divide(4,0,function(err,result){
        // did an error occur?
        if ( err ) {
            // handle the error safely
            console.log('4/0=err', err)
        }
        else {
            // no error occured, continue on
            console.log('4/0='+result)
        }
    })
    
  • Для eventful кода, где ошибка может произойти где угодно, вместо того, чтобы бросать ошибку, запустите error вместо:

    // Definite our Divider Event Emitter
    var events = require('events')
    var Divider = function(){
        events.EventEmitter.call(this)
    }
    require('util').inherits(Divider, events.EventEmitter)
    
    // Add the divide function
    Divider.prototype.divide = function(x,y){
        // if error condition?
        if ( y === 0 ) {
            // "throw" the error safely by emitting it
            var err = new Error("Can't divide by zero")
            this.emit('error', err)
        }
        else {
            // no error occured, continue on
            this.emit('divided', x, y, x/y)
        }
    
        // Chain
        return this;
    }
    
    // Create our divider and listen for errors
    var divider = new Divider()
    divider.on('error', function(err){
        // handle the error safely
        console.log(err)
    })
    divider.on('divided', function(x,y,result){
        console.log(x+'/'+y+'='+result)
    })
    
    // Divide
    divider.divide(4,2).divide(4,0)
    

Безопасные "ловушки" ошибок

Иногда, возможно, все еще есть код, который где-то вызывает ошибку, что может привести к неперехваченному исключению и потенциальному сбою нашего приложения, если мы не поймаем его безопасно. В зависимости от нашей архитектуры кода мы можем использовать один из следующих методов, чтобы поймать его:

  • Когда мы знаем, где происходит ошибка, мы можем обернуть этот раздел в node.js домен

    var d = require('domain').create()
    d.on('error', function(err){
        // handle the error safely
        console.log(err)
    })
    
    // catch the uncaught errors in this asynchronous or synchronous code block
    d.run(function(){
        // the asynchronous or synchronous code that we want to catch thrown errors on
        var err = new Error('example')
        throw err
    })
    
  • Если мы знаем, где происходит ошибка, это синхронный код и по какой-либо причине не может использовать домены (возможно, старую версию node), мы можем использовать инструкцию try catch:

    // catch the uncaught errors in this synchronous code block
    // try catch statements only work on synchronous code
    try {
        // the synchronous code that we want to catch thrown errors on
        var err = new Error('example')
        throw err
    } catch (err) {
        // handle the error safely
        console.log(err)
    }
    

    Однако будьте осторожны, чтобы не использовать try...catch в асинхронном коде, так как асинхронно запущенная ошибка не будет улавливаться:

    try {
        setTimeout(function(){
            var err = new Error('example')
            throw err
        }, 1000)
    }
    catch (err) {
        // Example error won't be caught here... crashing our app
        // hence the need for domains
    }
    

    Еще одна вещь, которую следует соблюдать с помощью try...catch, - это риск обернуть ваш обратный вызов завершения внутри оператора try следующим образом:

    var divide = function(x,y,next) {
        // if error condition?
        if ( y === 0 ) {
            // "throw" the error safely by calling the completion callback
            // with the first argument being the error
            next(new Error("Can't divide by zero"))
        }
        else {
            // no error occured, continue on
            next(null, x/y)
        }
    }
    
    var continueElsewhere = function(err, result){
            throw new Error('elsewhere has failed')
    }
    
    try {
            divide(4, 2, continueElsewhere)
            // ^ the execution of divide, and the execution of 
            //   continueElsewhere will be inside the try statement
    }
    catch (err) {
            console.log(err.stack)
            // ^ will output the "unexpected" result of: elsewhere has failed
    }
    

    Это очень легко сделать, поскольку ваш код становится более сложным. Таким образом, лучше всего использовать домены или возвращать ошибки, чтобы избежать (1) неперехваченных исключений в асинхронном коде (2) попытку захвата ловушки try, которую вы не хотите. В языках, которые позволяют правильно нарезать резьбу вместо стиля асинхронной анимации JavaScript, это не является проблемой.

  • Наконец, в случае, когда неперехваченная ошибка происходит в месте, которое не было завершено в домене или в инструкции try catch, мы можем сделать наше приложение не аварийным с помощью прослушивателя uncaughtException (однако поэтому можно поместить приложение в неизвестное состояние):

    // catch the uncaught errors that weren't wrapped in a domain or try catch statement
    // do not use this in modules, but only in applications, as otherwise we could have multiple of these bound
    process.on('uncaughtException', function(err) {
        // handle the error safely
        console.log(err)
    })
    
    // the asynchronous or synchronous code that emits the otherwise uncaught error
    var err = new Error('example')
    throw err
    

Ответ 2

Ниже приводится обобщение и сведение из разных источников по этой теме, включая пример кода и цитаты из выбранных сообщений в блоге. Полный список лучших практик можно найти здесь


Рекомендации по работе с ошибками Node.JS


Number1: используйте promises для обработки ошибок async

TL; DR: Обработка асинхронных ошибок в стиле обратного вызова, вероятно, является самым быстрым способом в ад (a.k.a - пирамида гибели). Лучший подарок, который вы можете дать вашему коду, использует вместо этого уважаемую библиотеку обещаний, которая обеспечивает очень компактный и знакомый синтаксис кода, такой как try-catch

В противном случае: Node.JS стиль обратного вызова, функция (err, response), является многообещающим способом для не поддерживаемого кода из-за сочетания обработки ошибок со случайным кодом, чрезмерной вложенности и неудобные шаблоны кодирования

Пример кода - хороший

doWork()
.then(doWork)
.then(doError)
.then(doWork)
.catch(errorHandler)
.then(verify);

пример кода anti pattern - обработка ошибок стиля обратного вызова

getData(someParameter, function(err, result){
    if(err != null)
      //do something like calling the given callback function and pass the error
    getMoreData(a, function(err, result){
          if(err != null)
            //do something like calling the given callback function and pass the error
        getMoreData(b, function(c){ 
                getMoreData(d, function(e){ 
                    ...
                });
            });
        });
    });
});

Цитата в блоге: "У нас проблема с promises" (Из блога pouchdb, занявшего 11 место по ключевым словам "Node Promises" )

"... И на самом деле, обратные вызовы делают что-то еще более зловещее: они лишают нас стека, что мы обычно принимаем как должное на языках программирования. Написание кода без стека во многом напоминает вождение автомобиля без тормоза педаль: вы не понимаете, насколько вам это нужно, пока вы не достигнете этого, а его нет. Вся суть promises заключается в том, чтобы вернуть нам основы языка, которые мы потеряли, когда мы пошли асинхронно: возвращение, бросок, и стек. Но вы должны знать, как правильно использовать promises, чтобы использовать их."


Number2: использовать только встроенный объект Error

TL; DR: Очень часто можно увидеть код, который выдает ошибки как строку или как пользовательский тип - это усложняет логику обработки ошибок и возможность взаимодействия между модулями. Независимо от того, отклоняете ли вы обещание, генерируете ли исключение или испускаете ошибку - используя Node.JS встроенный объект Error повышает единообразие и предотвращает потерю информации об ошибке

В противном случае: При выполнении какого-либо модуля, будучи неясным, какой тип ошибок приходит взамен - значительно сложнее рассуждать о предстоящем исключении и обрабатывать его. Даже стоит использовать пользовательские типы для описания ошибок, которые могут привести к потере критической информации об ошибке, такой как трассировка стека!

Пример кода - сделайте это правильно

    //throwing an Error from typical function, whether sync or async
 if(!productToAdd)
 throw new Error("How can I add new product when no value provided?");

//'throwing' an Error from EventEmitter
const myEmitter = new MyEmitter();
myEmitter.emit('error', new Error('whoops!'));

//'throwing' an Error from a Promise
 return new promise(function (resolve, reject) {
 DAL.getProduct(productToAdd.id).then((existingProduct) =>{
 if(existingProduct != null)
 return reject(new Error("Why fooling us and trying to add an existing product?"));

пример кода против шаблона

//throwing a String lacks any stack trace information and other important properties
if(!productToAdd)
    throw ("How can I add new product when no value provided?");

Цитата в блоге: "Строка не является ошибкой" (Из блога devthought, занял 6 место по ключевым словам "Node.JS объект ошибки" )

"... передача строки вместо ошибки приводит к уменьшенной функциональной совместимости между модулями. Она нарушает контракты с API, которые могут выполнять проверку экземпляров Errorof, или которые хотят узнать больше об ошибке. объекты, а также видят очень интересные свойства в современных машинах JavaScript, кроме того, что сообщение передано конструктору.."


Number3: Различать операционные и программные ошибки

TL; DR: Операционные ошибки (например, API, полученные недопустимым вводом) относятся к известным случаям, когда воздействие на ошибку полностью понимается и может обрабатываться вдумчиво. С другой стороны, ошибка программиста (например, попытка прочитать переменную undefined) относится к неизвестным ошибкам кода, которые диктуют изящно перезапустить приложение

В противном случае: Вы можете всегда перезапускать приложение при появлении ошибки, но почему нужно отключать онлайн-пользователей ~ 5000 из-за незначительной и прогнозируемой ошибки (операционная ошибка)? противоположное тоже не идеально - сохранение приложения, когда неизвестная проблема (ошибка программиста) может привести к непредсказуемому поведению. Дифференцирование двух позволяет действовать тактично и применять сбалансированный подход, основанный на данном контексте

Пример кода - сделайте это правильно

    //throwing an Error from typical function, whether sync or async
 if(!productToAdd)
 throw new Error("How can I add new product when no value provided?");

//'throwing' an Error from EventEmitter
const myEmitter = new MyEmitter();
myEmitter.emit('error', new Error('whoops!'));

//'throwing' an Error from a Promise
 return new promise(function (resolve, reject) {
 DAL.getProduct(productToAdd.id).then((existingProduct) =>{
 if(existingProduct != null)
 return reject(new Error("Why fooling us and trying to add an existing product?"));

пример кода - помечена ошибка как операционная (доверенная)

//marking an error object as operational 
var myError = new Error("How can I add new product when no value provided?");
myError.isOperational = true;

//or if you're using some centralized error factory (see other examples at the bullet "Use only the built-in Error object")
function appError(commonType, description, isOperational) {
    Error.call(this);
    Error.captureStackTrace(this);
    this.commonType = commonType;
    this.description = description;
    this.isOperational = isOperational;
};

throw new appError(errorManagement.commonErrors.InvalidInput, "Describe here what happened", true);

//error handling code within middleware
process.on('uncaughtException', function(error) {
    if(!error.isOperational)
        process.exit(1);
});

Цитата в блоге: "В противном случае вы рискуете состоянием" (Из блога debugable, занимает 3 место по ключевым словам "Node.JS uncaught exception" )

"... По самой природе того, как работает бросок в JavaScript, почти никогда не существует способа безопасно" подобрать, где вы остановились ", без утечки ссылок или создания каких-либо других undefined хрупких. Самый безопасный способ ответить на возникшую ошибку - закрыть процесс. Разумеется, на обычном веб-сервере у вас может быть много соединений открытым, и не разумно резко закрыть их, потому что ошибка была вызвана кем-то другим. Лучшим подходом является отправка ответа об ошибке на запрос, вызвавший ошибку, позволяя другим закончить в обычное время и прекратить прослушивание новых запросов у этого рабочего"


Number4: обрабатывать ошибки централизованно, но не внутри промежуточного программного обеспечения

TL; DR: Логика обработки ошибок, такая как почта для администратора и ведения журнала, должна быть инкапсулирована в выделенный и централизованный объект, чтобы все конечные точки (например, промежуточное программное обеспечение Express, cron jobs, unit-testing) вызовите, когда приходит ошибка.

В противном случае: Не обрабатывать ошибки в одном месте приведет к дублированию кода и, вероятно, к ошибкам, которые обрабатываются неправильно

Пример кода - типичный поток ошибок

//DAL layer, we don't handle errors here
DB.addDocument(newCustomer, (error, result) => {
    if (error)
        throw new Error("Great error explanation comes here", other useful parameters)
});

//API route code, we catch both sync and async errors and forward to the middleware
try {
    customerService.addNew(req.body).then(function (result) {
        res.status(200).json(result);
    }).catch((error) => {
        next(error)
    });
}
catch (error) {
    next(error);
}

//Error handling middleware, we delegate the handling to the centrzlied error handler
app.use(function (err, req, res, next) {
    errorHandler.handleError(err).then((isOperationalError) => {
        if (!isOperationalError)
            next(err);
    });
});

Цитата в блоге: "Иногда более низкие уровни не могут делать ничего полезного, кроме распространения ошибки для их вызывающего" (Из блога Joyent, занявшего 1 место по ключевым словам "Node.JS обработка ошибок" )

"... Вы можете в конечном итоге обработать одну и ту же ошибку на нескольких уровнях стека. Это происходит, когда более низкие уровни не могут сделать ничего полезного, кроме распространения ошибки на их вызывающего абонента, которая распространяет ошибку на вызывающего абонента и т.д. Часто, только вызывающий абонент верхнего уровня знает, каков соответствующий ответ, нужно ли повторить операцию, сообщить об ошибке пользователю или что-то еще. Но это не значит, что вы должны пытаться сообщать обо всех ошибках на один обратный вызов верхнего уровня, потому что этот callback сам не знает, в каком контексте произошла ошибка"


Number5: ошибки API документа с использованием Swagger

TL; DR:. Позвольте своим абонентам API узнать, какие ошибки могут возникнуть в ответ, чтобы они могли обрабатывать эти данные без сбоев. Обычно это делается с помощью фреймворков документации REST API, таких как Swagger

В противном случае: Клиент API может решить сбой и перезапуск только потому, что он получил обратно ошибку, которую он не мог понять. Примечание: вызывающий ваш API может быть вам (очень типичный в среде микросервисов)

Цитата в блоге: "Вы должны сообщить своим абонентам, какие ошибки могут произойти" (Из блога Joyent, занявшего 1 место по ключевым словам "Node.JS logging" )

... Мы говорили о том, как обращаться с ошибками, но когда вы пишете новую функцию, как вы доставляете ошибки в код, называемый вашей функцией?... Если вы не знаете, какие ошибки могут произойти или не знаете, что они означают, тогда ваша программа не может быть правильной, кроме как случайно. Поэтому, если вы пишете новую функцию, вы должны сообщить своим абонентам, какие ошибки могут произойти, и что они mea


Номер6: изящно завершите процесс, когда незнакомец придет в город

TL; DR: При возникновении неизвестной ошибки (ошибка разработчика, см. список лучших практик №3) - существует неопределенность в отношении здорового применения. Общепринятая практика предлагает перезагрузить процесс, используя "инструмент для начинающих, например Forever и PM2

В противном случае: Если обнаружено незнакомое исключение, некоторый объект может находиться в неисправном состоянии (например, эмитент событий, который используется глобально и не запускает события больше из-за некоторого внутреннего сбоя), и все будущие запросы могут терпеть неудачу или вести себя безумно

Пример кода - решение о сбое

//deciding whether to crash when an uncaught exception arrives
//Assuming developers mark known operational errors with error.isOperational=true, read best practice #3
process.on('uncaughtException', function(error) {
 errorManagement.handler.handleError(error);
 if(!errorManagement.handler.isTrustedError(error))
 process.exit(1)
});


//centralized error handler encapsulates error-handling related logic 
function errorHandler(){
 this.handleError = function (error) {
 return logger.logError(err).then(sendMailToAdminIfCritical).then(saveInOpsQueueIfCritical).then(determineIfOperationalError);
 }

 this.isTrustedError = function(error)
 {
 return error.isOperational;
 }

Цитата в блоге: "Есть три школы мыслей об обработке ошибок" (Из блога jsrecipes)

... В обработке ошибок в первую очередь есть три школы: 1. Позвольте программе сбой и перезапустите ее. 2. Обработайте все возможные ошибки и никогда не сбой. 3. Сбалансированный подход между двумя


Номер7: используйте зрелый журнал, чтобы увеличить видимость ошибок

TL; ДР:Набор зрелых инструментов ведения журнала, таких как Winston, Bunyan или Log4J, ускорит обнаружение и понимание ошибок. Поэтому забудьте о console.log.

В противном случае: Скимминг через console.logs или вручную через беспорядочный текстовый файл без инструментов запросов или достойный просмотрщик журналов может занять вас на работе до позднего времени

Пример кода - Winston logger в действии

//your centralized logger object
var logger = new winston.Logger({
 level: 'info',
 transports: [
 new (winston.transports.Console)(),
 new (winston.transports.File)({ filename: 'somefile.log' })
 ]
 });

//custom code somewhere using the logger
logger.log('info', 'Test Log Message with some parameter %s', 'some parameter', { anything: 'This is metadata' });

Цитата в блоге: "Позволяет определить несколько требований (для регистратора):" (Из блога strongblog)

... Позволяет определить несколько требований (для регистратора): 1. Отметьте каждую строку журнала. Этот пример довольно понятен - вы должны быть в состоянии сказать, когда произошла каждая запись в журнале. 2. Формат регистрации должен легко усваиваться как людьми, так и машинами. 3. Позволяет использовать несколько настраиваемых потоков назначения. Например, вы можете записывать журналы трассировки в один файл, но когда возникает ошибка, пишите в тот же файл, затем в файл ошибки и отправляйте электронное письмо одновременно...


Номер 8: обнаружение ошибок и простоев с использованием продуктов APM

TL; DR: Продукты мониторинга и производительности (a.k.a APM) проактивно оценивают вашу кодовую базу или API, чтобы они могли автоматически выявлять ошибки, сбои и медленные части, которые вы отсутствовали.

В противном случае:. Вы можете приложить большие усилия для измерения производительности и времени работы API, возможно, вы никогда не будете знать, какие ваши самые медленные части кода в реальном мире и как они влияют на UX

Цитата в блоге: "сегменты продуктов APM" (Из блога Йони Голдберг)

"... Продукты APM представляют собой 3 основных сегмента: 1. Мониторинг веб-сайтов или API -, которые постоянно отслеживают время безотказной работы и производительность через HTTP-запросы. Могут быть установлены через несколько минут.: Pingdom, Uptime Robot и новая реликвия 2. Code Instrument -, для чего требуется встроить агент в приложение, чтобы использовать функцию медленного обнаружения кода, статистику исключений, мониторинг производительности и многое другое. Ниже перечислены несколько выбранных соперников: новая реликвия, динамика приложений 3. Панель управления оперативной информацией -. Эта линейка продуктов ориентирована на облегчение работы команды ops с метрикой и кураторским контентом, что помогает легко оставаться на вершине производительности приложения. Обычно это связано с объединением нескольких источников информации (журналов приложений, журналов БД, журналов серверов и т.д.) И работы по проектированию передней панели. Ниже приведены несколько выбранных соперников: Datadog, Splunk"


Выше приведенная версия - см. здесь более передовые методы и примеры

Ответ 3

Вы можете поймать неперехваченные исключения, но это ограниченное использование. См. http://debuggable.com/posts/node-js-dealing-with-uncaught-exceptions:4c933d54-1428-443c-928d-4e1ecbdd56cb

monit, forever или upstart можно использовать для перезапуска процесса node при его сбое. Грациозное завершение работы - это то, на что вы можете надеяться (например, сохранить все данные в памяти в обработчике исключенных исключений).

Ответ 4

nodejs domains - это самый современный способ обработки ошибок в nodejs. Домены могут захватывать как ошибки/другие события, так и традиционно заброшенные объекты. Домены также предоставляют функциональные возможности для обработки обратных вызовов с ошибкой, переданной в качестве первого аргумента методом перехвата.

Как и при обычной обработке ошибок try/catch-style, обычно лучше всего бросать ошибки, когда они происходят, и блокировать области, где вы хотите изолировать ошибки от влияния на остальную часть кода. Способ "блокировать" эти области - вызвать domain.run с помощью функции как блока изолированного кода.

В синхронном коде вышеописанное достаточно - при возникновении ошибки вы либо разрешаете ее перебрасывать, либо вы ее поймаете и обрабатываете там, возвращая любые данные, которые вам нужно вернуть.

try {  
  //something
} catch(e) {
  // handle data reversion
  // probably log too
}

Когда ошибка возникает при асинхронном обратном вызове, вам также необходимо иметь возможность полностью обрабатывать откаты данных (общее состояние, внешние данные, например базы данных и т.д.). ИЛИ вы должны установить что-то, указывающее на то, что произошло исключение - где бы вы ни заботились об этом флаге, вам нужно дождаться завершения обратного вызова.

var err = null;
var d = require('domain').create();
d.on('error', function(e) {
  err = e;
  // any additional error handling
}
d.run(function() { Fiber(function() {
  // do stuff
  var future = somethingAsynchronous();
  // more stuff

  future.wait(); // here we care about the error
  if(err != null) {
    // handle data reversion
    // probably log too
  }

})});

Некоторые из этого выше кода уродливы, но вы можете создавать шаблоны для себя, чтобы сделать их красивее, например:

var specialDomain = specialDomain(function() {
  // do stuff
  var future = somethingAsynchronous();
  // more stuff

  future.wait(); // here we care about the error
  if(specialDomain.error()) {
    // handle data reversion
    // probably log too
  } 
}, function() { // "catch"
  // any additional error handling
});

ОБНОВЛЕНИЕ (2013-09):

Выше, я использую будущее, которое подразумевает семантика волокон, которая позволяет вам ждать фьючерсов в строке. Это фактически позволяет вам использовать традиционные блоки try-catch для всего, что я считаю лучшим способом. Однако вы не всегда можете это сделать (например, в браузере)...

Есть также фьючерсы, которые не требуют семантики волокон (которые затем работают с обычным JavaScript-браузером). Их можно назвать фьючерсами, promises или отложенными (я буду просто ссылаться на фьючерсы здесь). Библиотеки фьючерсов с открытым исходным кодом JavaScript позволяют распространять ошибки между фьючерсами. Только некоторые из этих библиотек позволяют корректно обрабатывать любое запущенное будущее, поэтому будьте осторожны.

Пример:

returnsAFuture().then(function() {
  console.log('1')
  return doSomething() // also returns a future

}).then(function() {
  console.log('2')
  throw Error("oops an error was thrown")

}).then(function() {
  console.log('3')

}).catch(function(exception) {
  console.log('handler')
  // handle the exception
}).done()

Это подражает нормальной попытке, хотя части являются асинхронными. Он будет печатать:

1
2
handler

Обратите внимание, что он не печатает '3', потому что выбрано исключение, которое прерывает этот поток.

Взгляните на синюю птицу promises:

Обратите внимание, что я не нашел много других библиотек, кроме тех, которые правильно обрабатывают заброшенные исключения. Например, jQuery отложен, например, нет - обработчик "fail" никогда не получит исключение, заставившее обработчик "then", который, на мой взгляд, является нарушителем транзакций.

Ответ 5

Я недавно написал об этом в http://snmaynard.com/2012/12/21/node-error-handling/. Новая функция node в версии 0.8 - это домены и позволяет объединить все формы обработки ошибок в одну упрощенную форму управления. Вы можете прочитать о них в моем сообщении.

Вы также можете использовать что-то вроде Bugsnag для отслеживания ваших неперехваченных исключений и получать уведомление по электронной почте, в чате или иметь билет, созданный для неперехваченного исключения (я соучредитель Bugsnag).

Ответ 6

Одним из примеров, когда использование try-catch может быть уместно, является использование цикла forEach. Это синхронно, но в то же время вы не можете просто использовать оператор return во внутренней области. Вместо этого подход try and catch может использоваться для возврата объекта Error в соответствующую область. Рассмотрим:

function processArray() {
    try { 
       [1, 2, 3].forEach(function() { throw new Error('exception'); }); 
    } catch (e) { 
       return e; 
    }
}

Это комбинация подходов, описанных выше @balupton.

Ответ 7

Я хотел бы добавить, что библиотека Step.js помогает обрабатывать исключения, всегда передавая их следующей функции. Поэтому на последнем этапе вы можете использовать функцию, которая проверяет наличие ошибок на любом из предыдущих шагов. Этот подход может значительно упростить обработку ошибок.

Ниже приведена цитата с страницы github:

любые выброшенные исключения пойманы и переданы в качестве первого аргумента следующая функция. Пока вы не устанавливаете функции обратного вызова inline ваши основные функции препятствуют тому, чтобы когда-либо были какие-либо исключения. Это очень важно для длинных серверов node.JS поскольку одно неперехваченное исключение может привести к тому, что весь сервер будет опущен.

Кроме того, вы можете использовать Step для управления исполнением скриптов, чтобы в качестве последнего шага иметь раздел очистки. Например, если вы хотите написать сборку script в Node и сообщить, сколько времени потребовалось для записи, последний шаг может сделать это (вместо того, чтобы пытаться выкопать последний обратный вызов).

Ответ 8

После прочтения этого сообщения некоторое время назад мне было интересно, можно ли использовать домены для обработки исключений на уровне api/function. Я хотел использовать их для упрощения кода обработки исключений в каждой асинхронной функции, которую я написал. Моя забота заключалась в том, что использование нового домена для каждой функции приведет к значительным накладным расходам. Моя домашняя работа, по-видимому, указывает на то, что накладные расходы минимальны, и в некоторых ситуациях производительность на самом деле лучше с доменами, чем с попыткой захвата.

http://www.lighthouselogic.com/#/using-a-new-domain-for-each-async-function-in-node/

Ответ 10

Ошибки поймания здесь очень хорошо обсуждаются, но стоит забыть записывать ошибки где-то, чтобы вы могли их просматривать и исправлять.

Bunyan - популярная структура ведения журнала для NodeJS - она ​​поддерживает запись множества различных мест вывода, что делает ее полезной для локальной отладки, если вы избегаете console.log. В обработчике ошибок домена вы можете вывести ошибку в файл журнала.

var log = bunyan.createLogger({
  name: 'myapp',
  streams: [
    {
      level: 'error',
      path: '/var/tmp/myapp-error.log'  // log ERROR to this file
    }
  ]
});

Это может занять много времени, если у вас есть много ошибок и/или серверов для проверки, поэтому можно было бы взглянуть на такой инструмент, как Raygun (отказ от ответственности, я работаю в Raygun), чтобы группировать ошибки вместе - или использовать их вместе, Если вы решили использовать Raygun в качестве инструмента, его тоже легко настроить тоже

var raygunClient = new raygun.Client().init({ apiKey: 'your API key' });
raygunClient.send(theError);

С помощью инструмента, такого как PM2 или навсегда, ваше приложение должно иметь возможность сбой, выходить из системы и перезагружаться без каких-либо серьезных проблем.