Node.js Лучшая практика обработки исключений - PullRequest
720 голосов
/ 05 сентября 2011

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

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

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

Ответы [ 10 ]

710 голосов
/ 06 сентября 2011

Обновление: у 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)
    

Безопасное «отлавливание» ошибок

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

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

    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
    
80 голосов
/ 18 апреля 2016

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


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


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

TL; DR: Обработка асинхронных ошибок в стиле обратного вызова, вероятно, является самым быстрым путем в ад (a.k.a пирамида гибели). Лучший подарок, который вы можете дать своему коду, - это использовать надежную библиотеку обещаний, которая обеспечивает очень компактный и знакомый синтаксис кода, такой как 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 место по ключевым словам "Node.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»)

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


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 в действии

//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-запросов. Может быть настроено за считанные минутыes. Ниже приведены несколько выбранных претендентов: Pingdom, Uptime Robot и New Relic 2.Инструментарий кода - семейство продуктов , для которых требуется встроить агент в приложение, чтобы обеспечить медленное обнаружение кода, статистику исключений, мониторинг производительности и многое другое.Ниже приведены несколько выбранных претендентов: New Relic, App Dynamics 3.Панель оперативного интеллекта - Эта линейка продуктов направлена ​​на то, чтобы упростить оперативную команду с помощью метрик и кураторского контента, который помогает легко оставаться на вершине производительности приложений.Обычно это включает в себя объединение нескольких источников информации (журналы приложений, журналы БД, журналы серверов и т. Д.) И предварительную работу по разработке панели мониторинга.Ниже приведены несколько выбранных претендентов: Datadog, Splunk "


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

29 голосов
/ 06 сентября 2011

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

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

14 голосов
/ 10 апреля 2013

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

12 голосов
/ 10 января 2013

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

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

4 голосов
/ 11 сентября 2012

Один случай, когда использование 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 выше.

3 голосов
/ 12 июня 2012

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

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

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

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

2 голосов
/ 10 сентября 2013

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

1 голос
/ 25 сентября 2015

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

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

1 голос
/ 06 сентября 2012

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

...