Node.js Лучшая практика обработки исключений

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

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

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

11 ответов

Решение

Обновление: у 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)
        }
    })
    
  • Для событийного кода, где ошибка может произойти где угодно, вместо того, чтобы выдать ошибку, запустите 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
    })
    
  • Если мы знаем, где происходит ошибка, это синхронный код и по какой-либо причине не можем использовать домены (возможно, старая версия узла), мы можем использовать оператор 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 в сочетании с асинхронным кодом, при запуске Node 7.4 или выше вы можете использовать async/await изначально написать ваши асинхронные функции.

    Еще одна вещь, чтобы быть осторожным с 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) выполнения перехвата попытки перехвата, который вам не нужен. В языках, которые допускают правильную многопоточность вместо асинхронного стиля обработки событий в 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
    

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


Лучшие практики обработки ошибок Node.JS


Number1: Используйте обещания для асинхронной обработки ошибок

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

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

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

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

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

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){ 
                    ...
                });
            });
        });
    });
});

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

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


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 error object")

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


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

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

В противном случае: вы всегда можете перезапустить приложение при появлении ошибки, но зачем подводить ~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);
});

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

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


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

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

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

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

//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").

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


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

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

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

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

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


Number6: Прервите процесс изящно, когда незнакомец приезжает в город

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. Сбалансированный подход между двумя


Number7: Используйте зрелый регистратор, чтобы увеличить видимость ошибок

TL;DR: набор зрелых инструментов ведения журналов, таких как 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. Позволяет для нескольких настраиваемых потоков назначения. Например, вы можете записывать журналы трассировки в один файл, но при возникновении ошибки запишите в тот же файл, затем в файл ошибок и одновременно отправьте электронное письмо...


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

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

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

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

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


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

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

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

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

Как и при обычной обработке ошибок в стиле try / catch, обычно лучше выбрасывать ошибки, когда они возникают, и блокировать области, в которых вы хотите изолировать ошибки от воздействия на остальную часть кода. Способ "заблокировать" эти области - вызвать 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-браузером). Их можно назвать фьючерсами, обещаниями или отсрочками (далее я просто буду ссылаться на фьючерсы). Библиотеки фьючерсов простого старого 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", потому что было сгенерировано исключение, которое прерывает этот поток.

Взгляните на обещания синей птицы:

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

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

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

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

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

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

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

Ниже приводится цитата со страницы GitHub:

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

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

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

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

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

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 или навсегда, ваше приложение должно быть способно аварийно завершить работу, выйти из системы и перезагрузиться без каких-либо серьезных проблем.

Если вы хотите использовать Сервисы в Ubuntu(Upstart): Узел как сервис в Ubuntu 11.04 с upstart, monit и forever.js

        getCountryRegionData: (countryName, stateName) => {
    let countryData, stateData

    try {
      countryData = countries.find(
        country => country.countryName === countryName
      )
    } catch (error) {
      console.log(error.message)
      return error.message
    }

    try {
      stateData = countryData.regions.find(state => state.name === stateName)
    } catch (error) {
      console.log(error.message)
      return error.message
    }

    return {
      countryName: countryData.countryName,
      countryCode: countryData.countryShortCode,
      stateName: stateData.name,
      stateCode: stateData.shortCode,
    }
  },
Другие вопросы по тегам