Desde o momento em que o Node.js foi revelado ao mundo, ele viu uma boa parte de ambos elogios e críticas . O debate ainda continua e não pode terminar tão cedo. O que muitas vezes esquecemos nesses debates é que toda linguagem de programação e plataforma é criticada com base em certas questões, que são criadas pela forma como usamos a plataforma. Independentemente de quão difícil o Node.js torne a escrita de código seguro e quão fácil ela torne a escrita de código altamente simultâneo, a plataforma já existe há um bom tempo e foi usada para construir um grande número de serviços da web robustos e sofisticados. Esses serviços da Web são bem dimensionados e comprovaram sua estabilidade por meio de sua resistência ao tempo na Internet.
No entanto, como qualquer outra plataforma, o Node.js é vulnerável a problemas e questões do desenvolvedor. Alguns desses erros degradam o desempenho, enquanto outros fazem o Node.js parecer inutilizável para qualquer coisa que você esteja tentando alcançar. Neste artigo, daremos uma olhada em dez erros comuns que os desenvolvedores novos em Node.js costumam cometer e como eles podem ser evitados para se tornarem um Node.js pro .
construção de aplicativos móveis com html css e javascript
JavaScript em Node.js (assim como no navegador) fornece um ambiente de thread único. Isso significa que não há duas partes de seu aplicativo executadas em paralelo; em vez disso, a simultaneidade é alcançada por meio do tratamento de operações vinculadas a E / S de maneira assíncrona. Por exemplo, uma solicitação de Node.js ao mecanismo de banco de dados para buscar algum documento é o que permite que o Node.js se concentre em alguma outra parte do aplicativo:
// Trying to fetch an user object from the database. Node.js is free to run other parts of the code from the moment this function is invoked.. db.User.get(userId, function(err, user) { // .. until the moment the user object has been retrieved here })
No entanto, um pedaço de código vinculado à CPU em uma instância Node.js com milhares de clientes conectados é tudo o que é necessário para bloquear o loop de eventos, fazendo todos os clientes esperarem. Os códigos vinculados à CPU incluem tentar classificar uma grande matriz, executar um loop extremamente longo e assim por diante. Por exemplo:
function sortUsersByAge(users) { users.sort(function(a, b) { return a.age Invocar esta função “sortUsersByAge” pode ser bom se executado em uma pequena matriz de “usuários”, mas com uma grande matriz, terá um impacto terrível no desempenho geral. Se isso é algo que absolutamente deve ser feito, e você tem certeza de que não haverá mais nada esperando no loop de eventos (por exemplo, se isso fosse parte de uma ferramenta de linha de comando que você está construindo com Node.js, e não importaria se tudo fosse executado de forma síncrona), então isso pode não ser um problema. No entanto, em uma instância de servidor Node.js que tenta atender milhares de usuários ao mesmo tempo, tal padrão pode ser fatal.
Se essa matriz de usuários estivesse sendo recuperada do banco de dados, a solução ideal seria buscá-la já classificada diretamente do banco de dados. Se o loop de evento estava sendo bloqueado por um loop escrito para calcular a soma de um longo histórico de dados de transações financeiras, ele poderia ser adiado para algum trabalhador externo / configuração de fila para evitar monopolizar o loop de evento.
Como você pode ver, não há solução mágica para esse tipo de problema do Node.js, em vez disso, cada caso precisa ser tratado individualmente. A ideia fundamental é não fazer trabalho intensivo de CPU nas instâncias frontais do Node.js - aquelas às quais os clientes se conectam simultaneamente.
Erro nº 2: Invocar um retorno de chamada mais de uma vez
JavaScript tem contado com callbacks desde sempre. Em navegadores da web, os eventos são tratados passando referências a funções (geralmente anônimas) que agem como retornos de chamada. No Node.js, os retornos de chamada costumavam ser a única maneira pela qual os elementos assíncronos do seu código se comunicavam - até que as promessas fossem apresentadas. Callbacks ainda estão em uso e os desenvolvedores de pacotes ainda projetam suas APIs em torno de callbacks. Um problema comum do Node.js relacionado ao uso de callbacks é chamá-los mais de uma vez. Normalmente, uma função fornecida por um pacote para fazer algo de forma assíncrona é projetada para esperar uma função como seu último argumento, que é chamado quando a tarefa assíncrona é concluída:
module.exports.verifyPassword = function(user, password, done) { if(typeof password !== ‘string’) { done(new Error(‘password should be a string’)) return } computeHash(password, user.passwordHashOpts, function(err, hash) { if(err) { done(err) return } done(null, hash === user.passwordHash) }) }
Observe como há uma instrução return toda vez que “done” é chamado, até a última vez. Isso ocorre porque chamar o retorno de chamada não encerra automaticamente a execução da função atual. Se o primeiro “retorno” foi comentado, passar uma senha diferente de string para esta função ainda resultará na chamada de “computeHash”. Dependendo de como “computeHash” lida com tal cenário, “concluído” pode ser chamado várias vezes. Qualquer pessoa usando essa função de outro lugar pode ser pego completamente desprevenido quando o retorno de chamada que passam for invocado várias vezes.
Ter cuidado é o suficiente para evitar esse erro do Node.js. Alguns desenvolvedores Node.js adotam o hábito de adicionar uma palavra-chave de retorno antes de cada chamada de retorno:
taxa horária média do contratante
if(err) { return done(err) }
Em muitas funções assíncronas, o valor de retorno quase não tem significado, portanto, essa abordagem geralmente torna mais fácil evitar esse tipo de problema.
Erro nº 3: Callbacks de aninhamento profundo
Callbacks de aninhamento profundo, muitas vezes referidos como 'inferno de callback', não é um problema do Node.js. No entanto, isso pode causar problemas fazendo com que o código saia rapidamente do controle:
function handleLogin(..., done) { db.User.get(..., function(..., user) { if(!user) { return done(null, ‘failed to log in’) } utils.verifyPassword(..., function(..., okay) { if(okay) { return done(null, ‘failed to log in’) } session.login(..., function() { done(null, ‘logged in’) }) }) }) }

Quanto mais complexa a tarefa, pior isso pode ficar. Aninhando callbacks dessa forma, facilmente terminamos com código sujeito a erros, difícil de ler e de manter. Uma solução alternativa é declarar essas tarefas como pequenas funções e, em seguida, vinculá-las. Embora, uma das (indiscutivelmente) soluções mais limpas para isso seja usar um pacote utilitário Node.js que lida com padrões de JavaScript assíncronos, como Async.js :
function handleLogin(done) { async.waterfall([ function(done) { db.User.get(..., done) }, function(user, done) { if(!user) { return done(null, ‘failed to log in’) } utils.verifyPassword(..., function(..., okay) { done(null, user, okay) }) }, function(user, okay, done) { if(okay) { return done(null, ‘failed to log in’) } session.login(..., function() { done(null, ‘logged in’) }) } ], function() { // ... }) }
Semelhante a “async.waterfall”, há uma série de outras funções que o Async.js fornece para lidar com diferentes padrões assíncronos. Para resumir, usamos exemplos mais simples aqui, mas a realidade costuma ser pior.
Erro nº 4: esperar que os retornos de chamada sejam executados de forma síncrona
A programação assíncrona com callbacks pode não ser algo exclusivo de JavaScript e Node.js, mas eles são responsáveis por sua popularidade. Com outras linguagens de programação, estamos acostumados com a ordem de execução previsível, onde duas instruções serão executadas uma após a outra, a menos que haja uma instrução específica para saltar entre as instruções. Mesmo assim, muitas vezes são limitados a instruções condicionais, instruções de loop e invocações de função.
No entanto, em JavaScript, com retornos de chamada, uma função específica pode não funcionar bem até que a tarefa que está esperando seja concluída. A execução da função atual será executada até o final sem qualquer parada:
function testTimeout() { console.log(“Begin”) setTimeout(function() { console.log(“Done!”) }, duration * 1000) console.log(“Waiting..”) }
Como você notará, chamar a função “testTimeout” imprimirá primeiro “Begin”, depois imprimirá “Waiting ..” seguido pela mensagem “Done!” depois de cerca de um segundo.
Tudo o que precisa acontecer depois que um retorno de chamada é acionado precisa ser invocado de dentro dele.
Erro # 5: atribuir a 'exportações', em vez de 'módulo.exportações'
O Node.js trata cada arquivo como um pequeno módulo isolado. Se o seu pacote tem dois arquivos, talvez “a.js” e “b.js”, então para “b.js” acessar a funcionalidade “a.js”, “a.js” deve exportá-lo adicionando propriedades ao o objeto de exportação:
// a.js exports.verifyPassword = function(user, password, done) { ... }
Quando isso for feito, qualquer pessoa que precisar de “a.js” receberá um objeto com a função de propriedade “verifyPassword”:
// b.js require(‘a.js’) // { verifyPassword: function(user, password, done) { ... } }
Porém, e se quisermos exportar essa função diretamente, e não como propriedade de algum objeto? Podemos substituir as exportações para fazer isso, mas não devemos tratá-las como uma variável global então:
// a.js module.exports = function(user, password, done) { ... }
Observe como estamos tratando “exportações” como uma propriedade do objeto de módulo. A distinção aqui entre “module.exports” e “extensions” é muito importante e costuma ser uma causa de frustração entre os novos desenvolvedores de Node.js.
Erro # 6: lançar erros de callbacks internos
JavaScript tem a noção de exceções. Imitando a sintaxe de quase todas as linguagens tradicionais com suporte ao tratamento de exceções, como Java e C ++, o JavaScript pode “lançar” e capturar exceções em blocos try-catch:
function slugifyUsername(username) { if(typeof username === ‘string’) { throw new TypeError(‘expected a string username, got '+(typeof username)) } // ... } try { var usernameSlug = slugifyUsername(username) } catch(e) { console.log(‘Oh no!’) }
No entanto, try-catch não se comportará como você poderia esperar em situações assíncronas. Por exemplo, se você quisesse proteger um grande pedaço de código com muita atividade assíncrona com um grande bloco try-catch, isso não funcionaria necessariamente:
consulta de mídia de web design responsivo
try { db.User.get(userId, function(err, user) { if(err) { throw err } // ... usernameSlug = slugifyUsername(user.username) // ... }) } catch(e) { console.log(‘Oh no!’) }
Se o retorno de chamada para “db.User.get” fosse disparado de forma assíncrona, o escopo contendo o bloco try-catch teria saído do contexto por muito tempo para ser capaz de capturar os erros lançados de dentro do retorno de chamada.
ajuste de desempenho no sql server 2008 passo a passo
É assim que os erros são tratados de uma maneira diferente no Node.js, e isso torna essencial seguir o padrão (err, ...) em todos os argumentos da função de retorno de chamada - o primeiro argumento de todos os retornos de chamada deve ser um erro se acontecer .
Erro nº 7: presumir que o número seja um tipo de dados inteiro
Os números em JavaScript são pontos flutuantes - não há tipo de dados inteiro. Você não esperaria que isso fosse um problema, já que números grandes o suficiente para enfatizar os limites da flutuação não são encontrados com frequência. É exatamente aí que os erros relacionados a isso acontecem. Uma vez que os números de ponto flutuante só podem conter representações inteiras até um certo valor, exceder esse valor em qualquer cálculo irá imediatamente começar a bagunçar tudo. Por mais estranho que possa parecer, o seguinte é avaliado como verdadeiro no Node.js:
Math.pow(2, 53)+1 === Math.pow(2, 53)
Infelizmente, as peculiaridades dos números em JavaScript não terminam aqui. Mesmo que os números sejam pontos flutuantes, os operadores que funcionam em tipos de dados inteiros também funcionam aqui:
5 % 2 === 1 // true 5 >> 1 === 2 // true
No entanto, ao contrário dos operadores aritméticos, os operadores bit a bit e os operadores shift funcionam apenas nos 32 bits à direita desses grandes números “inteiros”. Por exemplo, tentar deslocar “Math.pow (2, 53)” por 1 sempre resultará em 0. Tentar fazer um bit a bit - ou de 1 com o mesmo número grande resultará em 1.
Math.pow(2, 53) / 2 === Math.pow(2, 52) // true Math.pow(2, 53) >> 1 === 0 // true Math.pow(2, 53) | 1 === 1 // true
Você raramente precisa lidar com números grandes, mas se o fizer, há uma abundância de bibliotecas de grandes inteiros que implementam as operações matemáticas importantes em números de grande precisão, como node-bigint .
Erro nº 8: ignorar as vantagens das APIs de streaming
Digamos que queremos construir um pequeno servidor da web semelhante a um proxy que atende às solicitações, buscando o conteúdo de outro servidor da web. Como exemplo, devemos construir um pequeno servidor web que serve imagens do Gravatar:
var http = require('http') var crypto = require('crypto') http.createServer() .on('request', function(req, res) { var email = req.url.substr(req.url.lastIndexOf('/')+1) if(!email) { res.writeHead(404) return res.end() } var buf = new Buffer(1024*1024) http.get('http://www.gravatar.com/avatar/'+crypto.createHash('md5').update(email).digest('hex'), function(resp) { var size = 0 resp.on('data', function(chunk) { chunk.copy(buf, size) size += chunk.length }) .on('end', function() { res.write(buf.slice(0, size)) res.end() }) }) }) .listen(8080)
Neste exemplo específico de um problema com Node.js, estamos obtendo a imagem do Gravatar, lendo-a em um Buffer e, em seguida, respondendo à solicitação. Isso não é uma coisa ruim de se fazer, visto que as imagens do Gravatar não são muito grandes. No entanto, imagine se o tamanho do conteúdo que estamos fazendo proxy fosse de milhares de megabytes. Uma abordagem muito melhor teria sido esta:
http.createServer() .on('request', function(req, res) { var email = req.url.substr(req.url.lastIndexOf('/')+1) if(!email) { res.writeHead(404) return res.end() } http.get('http://www.gravatar.com/avatar/'+crypto.createHash('md5').update(email).digest('hex'), function(resp) { resp.pipe(res) }) }) .listen(8080)
Aqui, buscamos a imagem e simplesmente canalizamos a resposta para o cliente. Em nenhum momento precisamos ler todo o conteúdo em um buffer antes de servi-lo.
Erro nº 9: usar Console.log para fins de depuração
No Node.js, “console.log” permite que você imprima quase tudo no console. Passe um objeto para ele e ele o imprimirá como um literal de objeto JavaScript. Ele aceita qualquer número arbitrário de argumentos e os imprime todos ordenadamente separados por espaço. Existem vários motivos pelos quais um desenvolvedor pode se sentir tentado a usar isso para depurar seu código; entretanto, é altamente recomendável que você evite “console.log” em código real. Você deve evitar escrever “console.log” em todo o código para depurá-lo e comentá-los quando não forem mais necessários. Em vez disso, use uma das bibliotecas incríveis que são construídas apenas para isso, como depurar .
Pacotes como esses fornecem maneiras convenientes de ativar e desativar certas linhas de depuração ao iniciar o aplicativo. Por exemplo, com o debug, é possível evitar que qualquer linha de debug seja impressa no terminal não definindo a variável de ambiente DEBUG. Usar é simples:
// app.js var debug = require('debug')('app') debug('Hello, %s!', 'world')
Para habilitar as linhas de depuração, basta executar este código com a variável de ambiente DEBUG definida como “app” ou “*”:
DEBUG=app node app.js
Erro # 10: não usar programas de supervisor
Independentemente de o código Node.js estar em execução na produção ou no ambiente de desenvolvimento local, um monitor de programa supervisor que pode orquestrar seu programa é extremamente útil. Uma prática frequentemente recomendada por desenvolvedores de design e implementação de aplicativos modernos recomenda que seu código falhe rapidamente. Se ocorrer um erro inesperado, não tente manipulá-lo; em vez disso, deixe o programa travar e peça a um supervisor que o reinicie em alguns segundos. Os benefícios dos programas de supervisor não se limitam apenas ao reinício de programas travados. Essas ferramentas permitem que você reinicie o programa em caso de falha, bem como reinicie-os quando alguns arquivos forem alterados. Isso torna o desenvolvimento de programas em Node.js uma experiência muito mais agradável.
Há uma infinidade de programas de supervisor disponíveis para Node.js. Por exemplo:
o que é prototipagem em design
Todas essas ferramentas vêm com seus prós e contras. Alguns deles são bons para lidar com vários aplicativos na mesma máquina, enquanto outros são melhores no gerenciamento de logs. No entanto, se você deseja começar com esse programa, todas essas opções são justas.
Conclusão
Como você pode ver, alguns desses problemas do Node.js podem ter efeitos devastadores em seu programa. Alguns podem ser a causa da frustração enquanto você tenta implementar as coisas mais simples em Node.js. Embora o Node.js tenha tornado extremamente fácil para os novatos começarem, ele ainda possui áreas em que é igualmente fácil bagunçar. Os desenvolvedores de outras linguagens de programação podem ser capazes de se relacionar com alguns desses problemas, mas esses erros são bastante comuns entre os novos Desenvolvedores Node.js . Felizmente, eles são fáceis de evitar. Espero que este breve guia ajude os iniciantes a escrever um código melhor em Node.js e a desenvolver um software estável e eficiente para todos nós.