Manipulação de exceções de práticas recomendadas do Node.js.

755

Comecei a experimentar o node.js há alguns dias. Percebi que o Nó é encerrado sempre que tenho uma exceção não tratada no meu programa. Isso é diferente do contêiner de servidor normal ao qual fui exposto, onde apenas o Thread de Trabalho morre quando ocorrem exceções sem tratamento e o contêiner ainda poderá receber a solicitação. Isso levanta algumas questões:

  • É process.on('uncaughtException')a única maneira eficaz de se proteger contra isso?
  • Vai process.on('uncaughtException')pegar a exceção não tratada durante a execução de processos assíncronos bem?
  • Existe um módulo que já foi criado (como enviar e-mail ou gravar em um arquivo) que eu poderia aproveitar no caso de exceções não capturadas?

Eu apreciaria qualquer ponteiro / artigo que me mostrasse as práticas recomendadas comuns para lidar com exceções não capturadas no node.js

momo
fonte
11
exceções não capturadas não devem acontecer. Se eles usam um programa que reinicia o aplicativo inteiro quando seu bater (nodemon, para sempre, supervisor)
Raynos
116
Exceções.Ele sempre pode acontecer se você não colocar cada pedaço de seu interior código assíncrono try .. catch, e verificar isso também é feito para todos os seus libs
Dan
13
+1 Dan No começo, pensei que todas as suas bibliotecas eram um exagero, pois você "apenas" precisa agrupar todos os seus "pontos de entrada de encadeamentos" no código em try / catch. Mas, pensando nisso com mais cuidado, qualquer lib pode ter um setTimeoutou setIntervalalgo desse tipo enterrado em algum lugar profundo que não possa ser capturado pelo seu código.
Eugene Beresovsky 29/11
8
@EugeneBeresovksy Dan está certo, mas isso não muda o fato de que, quando ocorrem uncaughtExceptions, a única opção segura é reiniciar o aplicativo. Em outras palavras, seu aplicativo falhou e não há nada que você possa fazer ou deva fazer a respeito. Se você quiser fazer algo construtivo, implemente o novo recurso de domínio v0.8, ainda experimental, para registrar a falha e enviar uma resposta 5xx ao seu cliente.
Ostergaard
1
@ Dan Mesmo colocando todas as funções de retorno de chamada na tentativa .. catch não garante erros de captura. No caso de um módulo necessário usar seus próprios binários, eles podem travar sem graça. Isso aconteceu com o nó phantomjs, falhando em erros impossíveis de serem detectados (a menos que eu fizesse algum tipo de inspeção de processo nos binários necessários, mas nunca fiz isso).
Trindaz

Respostas:

737

Atualização: Joyent agora tem seu próprio guia . As informações a seguir são mais um resumo:

Erros "lançando" com segurança

Idealmente, gostaríamos de evitar erros não capturados o máximo possível; portanto, em vez de literalmente lançar o erro, podemos "com segurança" lançar o erro usando um dos métodos a seguir, dependendo da nossa arquitetura de código:

  • Para código síncrono, se ocorrer um erro, retorne o erro:

    // 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)
    }
  • Para código baseado em retorno de chamada (ou seja, assíncrono), o primeiro argumento do retorno de chamada é err, se ocorrer um erro erré o erro, se um erro não ocorrer, errserá null. Quaisquer outros argumentos seguem o errargumento:

    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)
        }
    })
  • Para código com eventos , em que o erro pode ocorrer em qualquer lugar, em vez de gerar o erro, inicie o errorevento :

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

Erros "pegando" com segurança

Às vezes, porém, ainda pode haver código que gera um erro em algum lugar, o que pode levar a uma exceção não capturada e a uma possível falha de nosso aplicativo se não o capturarmos com segurança. Dependendo da nossa arquitetura de código, podemos usar um dos seguintes métodos para capturá-lo:

  • Quando sabemos onde o erro está ocorrendo, podemos agrupar essa seção em um domínio 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
    })
  • Se soubermos onde o erro está ocorrendo, é um código síncrono e, por qualquer motivo, não puder usar domínios (talvez a versão antiga do nó), podemos usar a instrução 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)
    }

    No entanto, tenha cuidado para não usar try...catchno código assíncrono, pois um erro gerado de forma assíncrona não será detectado:

    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
    }

    Se você deseja trabalhar try..catchem conjunto com o código assíncrono, ao executar o Nó 7.4 ou superior, você pode usar async/awaitnativamente para gravar suas funções assíncronas.

    Outra coisa com a qual ter cuidado try...catché o risco de agrupar seu retorno de chamada de conclusão dentro da tryinstrução da seguinte maneira:

    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
    }

    Essa tarefa é muito fácil, pois seu código se torna mais complexo. Como tal, é melhor usar domínios ou retornar erros para evitar (1) exceções não capturadas no código assíncrono (2) a tentativa de captura de execução que você não deseja. Em idiomas que permitem o encadeamento adequado, em vez do estilo de máquina de eventos assíncrona do JavaScript, isso é menos problemático.

  • Finalmente, no caso de um erro não detectado ocorrer em um local que não foi envolvido em um domínio ou em uma instrução try catch, podemos fazer com que o nosso aplicativo não falhe usando o uncaughtExceptionouvinte (no entanto, isso pode colocar o aplicativo em um estado desconhecido ):

    // 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
balupton
fonte
5
Obrigado Raynos, atualizado. Você tem uma fonte que explica os males try catch? Como eu adoraria apoiar isso com evidências. Também foi corrigido o exemplo de sincronização.
balupton
2
Esta resposta não é mais válida. Domínios resolve esse problema (recomendado por node.js)
Gabriel Llamas
5
@balupton Erros devem ser gerados para o tratamento de erros. Definitivamente, eles NÃO devem ser evitados. Não há nada sobre eles que interrompa a execução do aplicativo ou qualquer outra coisa. Java e a maioria das outras linguagens modernas têm excelente suporte para exceções. Minha única conclusão, depois de ler algumas das postagens malformadas aqui, é que as pessoas não as entendem muito bem e têm medo delas. Medo da dúvida incerta. Este debate foi decidido conclusivamente a favor de exceções há pelo menos 20 anos.
#
22
Agora, os domínios foram descontinuados pelo io.js : " Este módulo está com descontinuação pendente. Depois que uma API de substituição for finalizada, este módulo ficará totalmente descontinuado ... Os usuários que precisam absolutamente ter a funcionalidade que os domínios fornecem podem confiar nele por enquanto, mas deve esperar migrar para uma solução diferente no futuro ".
Timothy Gu
5
A API do domínio está obsoleta agora ? Eles mencionam uma API de substituição - alguém sabe quando isso será lançado e como será?
UpTheCreek
95

A seguir, é apresentado um resumo e uma curadoria de várias fontes diferentes sobre este tópico, incluindo exemplos de código e citações de postagens de blog selecionadas. A lista completa de práticas recomendadas pode ser encontrada aqui


Práticas recomendadas para o tratamento de erros do Node.JS


Número1: use promessas para tratamento de erros assíncronos

TL; DR: o tratamento de erros assíncronos no estilo de retorno de chamada é provavelmente o caminho mais rápido para o inferno (também conhecido como pirâmide do destino). O melhor presente que você pode dar ao seu código é usar uma biblioteca de promessas respeitável, que fornece uma sintaxe de código muito compacta e familiar, como try-catch

Caso contrário: o estilo de retorno de chamada Node.JS, função (err, resposta), é uma maneira promissora de código não-sustentável, devido à mistura de tratamento de erros com código casual, aninhamento excessivo e padrões de codificação desajeitados

Exemplo de código - bom

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

exemplo de código anti-padrão - tratamento de erro no estilo de retorno de chamada

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

Citação do blog: "Temos um problema com promessas" (no blog pouchdb, classificado em 11 pelas palavras-chave "Promessas do nó")

"... E, de fato, os retornos de chamada fazem algo ainda mais sinistro: eles nos privam da pilha, o que geralmente é um dado adquirido nas linguagens de programação. Escrever código sem uma pilha é como dirigir um carro sem um pedal de freio: você não perceba o quanto você precisa disso, até chegar lá e ele não está lá.O objetivo das promessas é devolver-nos os fundamentos da linguagem que perdemos quando assíncronas: retorno, arremesso e pilha. precisa saber como usar as promessas corretamente para tirar proveito delas " .


Número2: use apenas o objeto de erro interno

TL; DR: É bastante comum ver código que gera erros como string ou como um tipo personalizado - isso complica a lógica de manipulação de erros e a interoperabilidade entre os módulos. Se você rejeita uma promessa, lança uma exceção ou emite um erro - o uso do objeto Erro interno do Node.JS aumenta a uniformidade e evita a perda de informações de erro.

Caso contrário: Ao executar algum módulo, a incerteza de que tipo de erro ocorre em retorno - dificulta muito o raciocínio e a manipulação da exceção que se aproxima. Mesmo vale a pena, usar tipos personalizados para descrever erros pode levar à perda de informações críticas sobre erros, como o rastreamento de pilha!

Exemplo de código - fazendo certo

    //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?"));

exemplo de código anti-padrão

//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?");

Citação do blog: "Uma string não é um erro" (no blog devthought, classificou 6 para as palavras-chave “Node.JS error object”)

"... passar uma string em vez de um erro resulta em menor interoperabilidade entre os módulos. Ele quebra contratos com APIs que podem estar executando instâncias de verificações de erros ou que desejam saber mais sobre o erro . Objetos de erro, como veremos, têm muito propriedades interessantes nos modernos mecanismos JavaScript, além de conter a mensagem passada ao construtor .. "


Número 3: Distinguir erros operacionais x programadores

TL; DR: erros de operações (por exemplo, a API recebeu uma entrada inválida) referem-se a casos conhecidos em que o impacto do erro é totalmente compreendido e pode ser tratado com cuidado. Por outro lado, o erro do programador (por exemplo, tentar ler variáveis ​​indefinidas) refere-se a falhas de código desconhecidas que determinam reiniciar o aplicativo normalmente.

Caso contrário: você sempre poderá reiniciar o aplicativo quando um erro aparecer, mas por que decepcionar ~ 5000 usuários on-line devido a um erro pequeno e previsto (erro operacional)? o contrário também não é o ideal - manter o aplicativo ativo quando ocorreu um problema desconhecido (erro do programador) pode levar a um comportamento imprevisível. A diferenciação dos dois permite agir com tato e aplicar uma abordagem equilibrada com base no contexto fornecido

Exemplo de código - fazendo certo

    //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?"));

exemplo de código - marcando um erro como operacional (confiável)

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

Citação do blog : "Caso contrário, você corre o risco do estado" (No blog depurável, classifique 3 para as palavras-chave "Exceção não capturada do Node.JS")

" ... Pela própria natureza de como o throw funciona no JavaScript, quase nunca existe uma maneira de" pegar de onde você parou "com segurança, sem vazar referências ou criar outro tipo de estado frágil indefinido. A maneira mais segura de responder a um erro gerado é encerrar o processo.Claro , em um servidor Web normal, você pode ter muitas conexões abertas, e não é razoável encerrá-las abruptamente porque um erro foi acionado por outra pessoa. envie uma resposta de erro à solicitação que acionou o erro, deixando os outros concluírem no horário normal e pare de ouvir novas solicitações nesse trabalhador "


Número4: manipular erros centralmente, através, mas não no middleware

TL; DR: A lógica de manipulação de erros, como correio para administrador e log, deve ser encapsulada em um objeto dedicado e centralizado que todos os terminais (por exemplo, middleware Express, tarefas cron, teste de unidade) chamam quando ocorre um erro.

Caso contrário: não manipular erros em um único local levará à duplicação de código e provavelmente a erros tratados incorretamente

Exemplo de código - um fluxo de erro típico

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

Citação do blog: "Às vezes, níveis mais baixos não podem fazer nada de útil, exceto propagar o erro ao chamador" (no blog Joyent, classificou 1 para as palavras-chave "Tratamento de erros do Node.JS")

"... Você pode acabar lidando com o mesmo erro em vários níveis da pilha. Isso acontece quando os níveis mais baixos não podem fazer nada de útil, exceto propagar o erro ao chamador, que propaga o erro ao chamador e assim por diante. Frequentemente, somente o chamador de nível superior sabe qual é a resposta apropriada, seja para repetir a operação, relatar um erro ao usuário ou outra coisa, mas isso não significa que você deve tentar relatar todos os erros em um único nível superior retorno de chamada, porque esse retorno de chamada em si não pode saber em que contexto ocorreu o erro "


Número5: documentar erros da API usando o Swagger

TL; DR: informe aos chamadores da API quais erros podem resultar em retorno, para que eles possam lidar com eles com cuidado sem travar. Isso geralmente é feito com estruturas de documentação da API REST como o Swagger

Caso contrário: um cliente de API pode decidir travar e reiniciar apenas porque recebeu de volta um erro que não conseguia entender. Nota: o chamador da sua API pode ser você (muito típico em um ambiente de microsserviços)

Citação do blog: "Você precisa informar aos chamadores quais erros podem ocorrer" (no blog Joyent, classificado em 1 pelas palavras-chave “Registro no Node.JS”)

... Falamos sobre como lidar com erros, mas quando você está escrevendo uma nova função, como você entrega erros para o código que chamou sua função? … Se você não sabe quais erros podem acontecer ou não sabe o que eles significam, seu programa não pode estar correto, exceto por acidente. Portanto, se você estiver escrevendo uma nova função, precisará informar aos chamadores quais erros podem acontecer e o que eles significam


Número6: Encerre o processo normalmente quando um estranho chegar à cidade

TL; DR: Quando ocorre um erro desconhecido (um erro de desenvolvedor, consulte a prática recomendada número 3) - há incerteza sobre a integridade do aplicativo. Uma prática comum sugere reiniciar o processo com cuidado usando uma ferramenta 'restarter' como Forever e PM2

Caso contrário: Quando uma exceção desconhecida é capturada, algum objeto pode estar com um estado defeituoso (por exemplo, um emissor de evento que é usado globalmente e não dispara mais eventos devido a alguma falha interna) e todas as solicitações futuras podem falhar ou se comportar loucamente

Exemplo de código - decidindo se deve travar

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

Citação do blog: "Existem três pensamentos sobre o tratamento de erros" (do blog jsrecipes)

… Existem basicamente três escolas de pensamento sobre o tratamento de erros: 1. Deixe o aplicativo travar e reinicie-o. 2. Manipule todos os erros possíveis e nunca trava. 3. Abordagem equilibrada entre os dois


Número7: Use um criador de logs maduro para aumentar a visibilidade dos erros

TL; DR: Um conjunto de ferramentas de registro maduras, como Winston, Bunyan ou Log4J, acelerará a descoberta e o entendimento de erros. Então esqueça o console.log.

Caso contrário: percorrer o console.logs ou manualmente o arquivo de texto confuso, sem consultar as ferramentas ou um visualizador de logs decente, poderá mantê-lo ocupado no trabalho até tarde

Exemplo de código - Winston logger em ação

//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' });

Citação do blog: "Vamos identificar alguns requisitos (para um criador de logs):" (Do blog forte do blog)

... Vamos identificar alguns requisitos (para um registrador): 1. Carimbo de data e hora em cada linha de registro. Essa é bastante autoexplicativa - você deve saber quando cada entrada de log ocorreu. 2. O formato de registro deve ser facilmente digerível tanto por humanos quanto por máquinas. 3. Permite vários fluxos de destino configuráveis. Por exemplo, você pode estar gravando logs de rastreamento em um arquivo, mas quando um erro for encontrado, escreva no mesmo arquivo, depois no arquivo de erro e envie um email ao mesmo tempo ...


Número8: Descubra erros e tempo de inatividade usando produtos APM

TL; DR: os produtos de monitoramento e desempenho (também conhecidos como APM) medem proativamente sua base de código ou API para que possam destacar automaticamente magicamente erros, falhas e partes lentas que estavam faltando

Caso contrário: você pode se esforçar bastante para medir o desempenho e os tempos de inatividade da API, provavelmente nunca saberá quais são as partes de código mais lentas no cenário do mundo real e como isso afeta o UX

Citação do blog: "segmentos de produtos APM" (do blog Yoni Goldberg)

"… Os produtos APM constituem três segmentos principais: 1. Monitoramento de site ou API - serviços externos que monitoram constantemente o tempo de atividade e o desempenho via solicitações HTTP. Podem ser configurados em alguns minutos. A seguir, são apresentados alguns candidatos selecionados: Pingdom, Uptime Robot e New Relic 2 Instrumentação de código - família de produtos que exige a incorporação de um agente no aplicativo para beneficiar a detecção lenta de código, estatísticas de exceções, monitoramento de desempenho e muito mais A seguir, são apresentados alguns candidatos selecionados: New Relic, App Dynamics 3. Painel de inteligência operacional -essas linhas de produtos estão focadas em facilitar a equipe de operações com métricas e conteúdo com curadoria que ajuda a manter-se atualizado sobre o desempenho do aplicativo. Isso geralmente envolve a agregação de várias fontes de informações (logs de aplicativos, logs de banco de dados, log de servidores, etc.) e trabalho inicial de design de painéis. A seguir estão alguns candidatos selecionados: Datadog, Splunk "


A descrição acima é uma versão reduzida - veja aqui mais práticas recomendadas e exemplos

Yonatan
fonte
30

Você pode capturar exceções não capturadas, mas é de uso limitado. Consulte http://debuggable.com/posts/node-js-dealing-with-uncaught-exceptions:4c933d54-1428-443c-928d-4e1ecbdd56cb

monit, foreverou upstartpode ser usado para reiniciar o processo do nó quando ele falha. Um desligamento normal é o melhor que você pode esperar (por exemplo, salve todos os dados na memória no manipulador de exceções não capturado).

nponeccop
fonte
4
+1 O link é útil, obrigado. Eu ainda estou procurando a melhor prática e o significado de "graciosa reiniciar" no contexto de node.js
momo
Meu entendimento de "reinício gracioso" neste contexto seria essencialmente o que o nponeccop sugere: deixe o processo morrer e deixe o que estiver executando em primeiro lugar, reinicie-o.
Ilkka
Muito obrigado por esse link! Muito útil!
precisa saber é o seguinte
Esta é uma ótima resposta. Eu discordaria em retornar um erro no seu primeiro exemplo. Retornar an Errortorna polimórfico o valor de retorno que atrapalha desnecessariamente a semântica da função. Além disso, por mergulho 0 já é tratado no JavaScript, dando Infinity, -Infinity, ou NaN, se for caso valores typeof === 'number'. Eles podem ser verificados com !isFinite(value). Em geral, eu recomendaria nunca retornar um erro de uma função. Melhor em termos de legibilidade e manutenção do código, para lançar ou retornar um valor não polimórfico especial com semântica consistente.
wprl
O link está quebrado. downforeveryoneorjustme.com/debuggable.com
Kev
13

Os domínios do nodejs são a maneira mais atualizada de lidar com erros no nodejs. Os domínios podem capturar eventos de erro / outros, bem como objetos lançados tradicionalmente. Os domínios também fornecem funcionalidade para lidar com retornos de chamada com um erro passado como o primeiro argumento por meio do método de interceptação.

Como na manipulação normal de erros no estilo try / catch, geralmente é melhor lançar erros quando eles ocorrem e bloquear áreas nas quais você deseja isolar os erros de afetar o restante do código. A maneira de "bloquear" essas áreas é chamar domain.run com uma função como um bloco de código isolado.

No código síncrono, o acima é suficiente - quando ocorre um erro, você o deixa passar, ou o pega e lida com ele, revertendo todos os dados necessários.

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

Quando o erro ocorre em um retorno de chamada assíncrono, você precisa ser capaz de lidar totalmente com a reversão de dados (estado compartilhado, dados externos como bancos de dados, etc.). OU você deve definir algo para indicar que ocorreu uma exceção - sempre que você se importar com esse sinalizador, precisará aguardar a conclusão do retorno de chamada.

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
  }

})});

Alguns dos códigos acima são feios, mas você pode criar padrões para torná-lo mais bonito, por exemplo:

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

ATUALIZAÇÃO (2013-09):

Acima, eu uso um futuro que implica semântica de fibras , o que permite que você espere futuros em linha. Na verdade, isso permite que você use blocos tradicionais de try-catch para tudo - o que eu acho o melhor caminho a percorrer. No entanto, você nem sempre pode fazer isso (ou seja, no navegador) ...

Também existem futuros que não requerem semântica de fibras (que funcionam com JavaScript normal de navegação). Estes podem ser chamados de futuros, promessas ou diferidos (vou me referir a futuros daqui em diante). As bibliotecas de futuros JavaScript simples e antigos permitem que os erros sejam propagados entre futuros. Somente algumas dessas bibliotecas permitem que qualquer futuro lançado seja manipulado corretamente, portanto, tenha cuidado.

Um exemplo:

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

Isso imita um try-catch normal, mesmo que as peças sejam assíncronas. Seria impresso:

1
2
handler

Observe que ele não imprime '3' porque foi lançada uma exceção que interrompe esse fluxo.

Dê uma olhada nas promessas do bluebird:

Observe que não encontrei muitas outras bibliotecas além dessas que lidam adequadamente com exceções lançadas. O jQuery é adiado, por exemplo, não - o manipulador "fail" nunca receberia a exceção de um manipulador a 'then', o que, na minha opinião, é um rompimento de negócio.

BT
fonte
A especificação adequada de promessas em Javascript é conhecida como Promises / A +. Você pode ver uma lista de implementações aqui: github.com/promises-aplus/promises-spec/blob/master/… . Observe que um Promises / A + simples é inutilizável na prática - o Promises / A + ainda deixa muitos problemas práticos para as bibliotecas se resolverem. No entanto, coisas absolutamente essenciais, como a propagação do erro exibida, a ordem de execução determinística e a segurança contra o estouro da pilha são garantidas.
Esailija
11

Eu escrevi sobre isso recentemente em http://snmaynard.com/2012/12/21/node-error-handling/ . Um novo recurso do nó na versão 0.8 é domínios e permite combinar todas as formas de tratamento de erros em um formulário de gerenciamento mais fácil. Você pode ler sobre eles no meu post.

Você também pode usar algo como o Bugsnag para rastrear suas exceções não capturadas e ser notificado por e-mail, sala de bate-papo ou criar um ticket para uma exceção não capturada (eu sou o co-fundador da Bugsnag).

Simon Maynard
fonte
2
O módulo de domínio agora está oficialmente obsoleto. nodejs.org/api/domain.html
MattSidor 02/04
3

Gostaria apenas de adicionar que a biblioteca Step.jx ajuda a lidar com exceções, sempre passando-a para a próxima etapa. Portanto, você pode ter como último passo uma função que verifica se há erros em qualquer uma das etapas anteriores. Essa abordagem pode simplificar bastante o tratamento de erros.

Abaixo está uma citação da página do github:

quaisquer exceções lançadas são capturadas e passadas como o primeiro argumento para a próxima função. Contanto que você não aninhe funções de retorno de chamada em linha, suas funções principais evitam que ocorram exceções não capturadas. Isso é muito importante para servidores node.JS de longa execução, pois uma única exceção não capturada pode derrubar todo o servidor.

Além disso, você pode usar o Step para controlar a execução de scripts para ter uma seção de limpeza como o último passo. Por exemplo, se você deseja escrever um script de construção no Node e relatar quanto tempo levou para escrever, a última etapa pode fazer isso (em vez de tentar descobrir o último retorno de chamada).

Michael Yagudaev
fonte
3

Uma instância em que o uso de um try-catch pode ser apropriado é o uso de um loop forEach. É síncrono, mas ao mesmo tempo você não pode usar apenas uma declaração de retorno no escopo interno. Em vez disso, uma abordagem try and catch pode ser usada para retornar um objeto Error no escopo apropriado. Considerar:

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

É uma combinação das abordagens descritas por @balupton acima.

Michael Yagudaev
fonte
Em vez de gerar erros, alguns desenvolvedores recomendam o uso do conceito Result da Rust para retornar OK ou Fail , quando a falha é uma possibilidade conhecida. Isso mantém as falhas separadas dos erros inesperados. Uma implementação JS disso é r-result .
Joeytwiddle 19/06/2016
É uma decisão de design para todo o aplicativo. Eu acho que seu conceito de retornar erros é aproximadamente equivalente e simples de começar (sem dependências extras), mas menos explícito (o resultado o deixa dolorosamente ciente de que falhas precisam ser tratadas) e menos eficiente nos casos em que uma pilha é construído desnecessariamente.
joeytwiddle
1

Depois de ler este post há algum tempo, fiquei pensando se era seguro usar domínios para manipulação de exceções em um nível de API / função. Eu queria usá-los para simplificar o código de manipulação de exceções em cada função assíncrona que escrevi. Minha preocupação era que o uso de um novo domínio para cada função introduzisse uma sobrecarga significativa. Minha lição de casa parece indicar que há uma sobrecarga mínima e que o desempenho é realmente melhor com domínios do que com try catch em algumas situações.

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

Sudsy
fonte
1

A captura de erros foi muito bem discutida aqui, mas vale lembrar de registrar os erros em algum lugar para que você possa vê-los e consertar as coisas.

Bunyan é uma estrutura de log popular para o NodeJS - ela suporta escrever em vários locais de saída diferentes, o que a torna útil para depuração local, desde que você evite o console.log. No manipulador de erros do seu domínio, você pode cuspir o erro em um arquivo de log.

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

Isso pode consumir muito tempo se você tiver muitos erros e / ou servidores para verificar, então pode valer a pena procurar uma ferramenta como Raygun (aviso de isenção de responsabilidade, trabalho na Raygun) para agrupar erros - ou usá-los juntos. Se você decidiu usar o Raygun como uma ferramenta, é muito fácil configurar também

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

Além de usar uma ferramenta como o PM2 ou para sempre, seu aplicativo deve travar, desconectar o que aconteceu e reiniciar sem grandes problemas.

K. Craven
fonte