Node.js developers Ten common mistakes

Foreword

Since the launch of Node.js, it gets a lot of praise and criticism. This debate will continue until a short time will not end. In these debates, we often ignore all languages ​​and platforms are based on some of the core issues to criticize, that is, how do we use these platforms. And how simple matter to write reliable code using Node.js how difficult, high concurrent code is written, the platform after all there was a period of time, and it is used to create a large number of robust and complex web service. These web services not only has good scalability, and proven their robustness on the Internet through the duration.

However, as other platforms, like, Node.js developers can easily make mistakes. Some of these errors will reduce the performance of the program, Node.js will lead to some unusable. In this article, we will see ten kinds of error Node.js newbie, and how to avoid them.

Error 1: blocking event loop

Node.js (as a browser) in the JavaScript provides a single-threaded environment. This means that your program does not have to run two things at the same time, instead of asynchronous processing is I / O intensive operations brought about concurrency. For example, when the database Node.js to initiate a request to get some data, Node.js can concentrate on other parts of the program:

// 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
})
复制代码

However, in an example of Node.js thousands of client connections, the short CPU computationally intensive code will clog live event loop, leading to all clients had to wait. CPU compute-intensive code includes an attempt to sort a huge array, run a very long time-consuming functions, and so on. E.g:

function sortUsersByAge(users) {
	users.sort(function(a, b) {
		return a.age < b.age ? -1 : 1
	})
}
复制代码

Call "sortUsersByAge" method on a small "users" of the array is no problem, but if it is on a large array, it will have a huge impact on overall performance. If this thing had to do, and you can make sure that no other events on the event loop waiting (such as Node.js This is just a command-line tool, but it does not matter all things are synchronized to work), then, that this is no problem. However, in the case of a Node.js server trying to serve thousands of users simultaneously, it will cause problems.

If the users array is fetched from the database, then the ideal solution is to have sorted out the data from the database. If the event is a circulating loop to calculate the sum of the historical financial transaction data blocked, this cycle is calculated to be pushed outside the event loop execution queue so as not to take up the event loop.

As you can see, there is no silver bullet to solve this type of error, it can only be resolved separately for each case. The basic idea is not to deal with clients in Node.js instances of concurrent connections on the CPU to do compute-intensive work.

Error 2: Multiple calls a callback function

JavaScript has always been dependent on the callback function. In the browser, the events are passed by reference event object to a callback function (usually anonymous function) handled by. In Node.js, the callback function once the only way asynchronous communication with other code until the promise appears. The callback function is now still in use, and many developers are still around it to set their API. A common error associated with the use of callback function is to call them many times. Typically, a method of packaging a number of asynchronous processing, it is the last parameter is designed as a transfer function that is called after processing the asynchronous:

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)
	})
}
复制代码

Here we note that except for the last time, every time "done" after the method is called, there will be a return statement. This is because the callback function does not automatically end the execution of the current method. If we comment out the first return statement, then pass a non-string type of password to this function, we will still end with a call computeHash method. According computeHash approach in this case, "done" will be called multiple times. When the last pass of the callback function is called multiple times, anyone will be blindsided.

Avoid this problem only need to be careful. Some Node.js developers therefore develop a habit, plus a return keyword before all callback function statement:

IF (ERR) { 
	return DONE (ERR) 
} 
copy the code

In many asynchronous functions, this kind of return the return value is meaningless, so such a move just to simply avoid this error only.

Error 3: deeply nested callback function

Deeply nested callback function is usually known as the "callback hell", which in itself is not a problem, but it can lead to code quickly become out of control:

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’)
			})
		})
	})
}
复制代码

The more complex the task, the greater the harm. Like nested callbacks, our program is prone to error, and the code difficult to read and maintain. These tasks are a stopgap measure is to be declared as one of the small function, and then link them together. However, (there may be) one of the easiest solution is to use a Node.js public assembly to deal with such asynchronous js, such as 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() {
		// ...
	})
}
复制代码

Async.js also offers many similar "async.waterfall" approach to deal with different asynchronous scenarios. For simplicity, here we demonstrate a simple example, the reality is often more complex.

(Post ads next door "ES6 Generator Introduction" mentioned Generator can be resolved callback hell Oh, and the use of more natural combination Promise, please look forward to the next article, right next door to the landlord: D)

Error 4: expect a callback function to perform synchronization

Asynchronous callback function does not just have JavaScript and Node.js, but they let this induction program became popular. In other programming languages, we used two statements executed one after another, have a special jump instruction unless between the two statements. Even then, these were limited by conditional statements, looping statements and function calls.

However, in JavaScript, the callback method with a callback function until the completion of the task may not be completed. The current function has been executed in the end:

function testTimeout() {
	console.log(“Begin”)
	setTimeout(function() {
		console.log(“Done!”)
	}, duration * 1000)
	console.log(“Waiting..”)
}
复制代码

You may notice, calling "testTimeout" function to output "Begin", then output "Waiting ..", followed by a few seconds after the output "Done!".

Before any code to be executed in the callback function is completed, we need to call in the callback function.

Error 5: to "exports" assignment, rather than "module.exports"

Node.js though each file is a separate module. If your bag has two files, the assumption is "a.js" and "b.js", then "b.js" to use "a.js" function, "a.js" must pass to exports target Adding properties to expose these features:

// a.js
exports.verifyPassword = function(user, password, done) { ... }
复制代码

完成这步后,所有需要“a.js” 的都会获得一个带有“verifyPassword” 函数属性的对象:

// b.js
require(‘a.js’) // { verifyPassword: function(user, password, done) { ... } } 
复制代码

然而,如果我们想直接暴露这个函数,而不是让它作为某些对象的属性呢?我们可以覆写 exports 来达到目的,但是我们绝对不能把它当做一个全局变量:

// a.js
module.exports = function(user, password, done) { ... }
复制代码

注意到我们是把“exports” 当做 module 对象的一个属性。“module.exports” 和“exports” 这之间区别是很重要的,而且经常会使 Node.js 新手踩坑。

错误6:从回调里抛出错误

JavaScript 有异常的概念。在语法上,学绝大多数传统语言(如 Java、C++)对异常的处理那样,JavaScript 可以抛出异常以及在 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!’)
}
复制代码

然而,在异步环境下,tary-catch 可能不会像你所想的那样。比如说,如果你想用一个大的 try-catch 去保护一大段含有许多异步处理的代码,它可能不会正常的工作:

try {
	db.User.get(userId, function(err, user) {
		if(err) {
			throw err
		}
		// ...
		usernameSlug = slugifyUsername(user.username)
		// ...
	})
} catch(e) {
	console.log(‘Oh no!’)
}
复制代码

如果“db.User.get” 的回调函数异步执行了,那么 try-catch 原来所在的作用域就很难捕获到回调函数里抛出的异常了。

这就是为什么在 Node.js 里通常使用不同的方式处理错误,而且这使得所有回调函数的参数都需要遵循 (err, ...) 这种形式,其中第一个参数是错误发生时的 error 对象。

错误7:认为 Number 是一种整型数据格式

在 JavaScript 里数字都是浮点型,没有整型的数据格式。你可能认为这不是什么问题,因为数字大到溢出浮点型限制的情况很少出现。可实际上,当这种情况发生时就会出错。因为浮点数在表达一个整型数时只能表示到一个最大上限值,在计算中超过这个最大值时就会出问题。也许看起来有些奇怪,但在 Node.js 中下面代码的值是 true:

Math.pow(2, 53)+1 === Math.pow(2, 53)
复制代码

很不幸的是,JavaScript 里有关数字的怪癖可还不止这些。尽管数字是浮点型的,但如下这种整数运算能正常工作:

5 % 2 === 1 // true
5 >> 1 === 2 // true
复制代码

然而和算术运算不同的是,位运算和移位运算只在小于 32 位最大值的数字上正常工作。例如,让“Math.pow(2, 53)” 位移 1 位总是得到 0,让其与 1 做位运算也总是得到 0:

Math.pow(2, 53) / 2 === Math.pow(2, 52) // true
Math.pow(2, 53) >> 1 === 0 // true
Math.pow(2, 53) | 1 === 0 // true
复制代码

你可能极少会去处理如此大的数字,但如果你需要的话,有很多实现了大型精密数字运算的大整数库可以帮到你,比如 node-bigint。

错误8:忽略了流式 API 的优势

现在我们想创建一个简单的类代理 web 服务器,它能通过拉取其他 web 服务器的内容来响应和发起请求。作为例子,我们创建一个小型 web 服务器为 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)
复制代码

在这个例子里,我们从 Gravatar 拉取图片,将它存进一个 Buffer 里,然后响应请求。如果 Gravatar 的图片都不是很大的话,这样做没问题。但想象下如果我们代理的内容大小有上千兆的话,更好的处理方式是下面这样:

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)
复制代码

这里我们只是拉取图片然后简单地以管道方式响应给客户端,而不需要在响应它之前读取完整的数据存入缓存。

错误9:出于 Debug 的目的使用 Console.log

在 Node.js 里,“console.log” 允许你打印任何东西到控制台上。比如传一个对象给它,它会以 JavaScript 对象的字符形式打印出来。它能接收任意多个的参数并将它们以空格作为分隔符打印出来。有很多的理由可以解释为什么开发者喜欢使用它来 debug 他的代码,然而我强烈建议你不要在实时代码里使用“console.log”。你应该要避免在所有代码里使用“console.log” 去 debug,而且应该在不需要它们的时候把它们注释掉。你可以使用一种专门做这种事的库代替,比如 debug。

这些库提供了便利的方式让你在启动程序的时候开启或关闭具体的 debug 模式,例如,使用 debug 的话,你能够阻止任何 debug 方法输出信息到终端上,只要不设置 DEBUG 环境变量即可。使用它十分简单:

// app.js
var debug = require(‘debug’)(‘app’)
debug(’Hello, %s!’, ‘world’)
复制代码

开启 debug 模式只需简单地运行下面的代码把环境变量 DEBUG 设置到“app” 或“*” 上:

DEBUG=app node app.js
复制代码

错误10:不使用监控程序

不管你的 Node.js 代码是跑在生产环境或是你的本地开发环境,一个能协调你程序的监控程序是十分值得拥有的。一条经常被开发者提及的,针对现代程序设计和开发的建议是你的代码应该有 fail-fast 机制。如果发生了一个意料之外的错误,不要尝试去处理它,而应该让你的程序崩溃然后让监控程序在几秒之内重启它。监控程序的好处不只是重启崩溃的程序,这些工具还能让你在程序文件发生改变的时候重启它,就像崩溃重启那样。这让开发 Node.js 程序变成了一个更加轻松愉快的体验。

Node.js 有太多的监控程序可以使用了,例如:

pm2

forever

nodemon

supervisor

所有这些工具都有它的优缺点。一些擅长于在一台机器上处理多个应用程序,而另一些擅长于日志管理。不管怎样,如果你想开始写一个程序,这些都是不错的选择。

总结

你可以看到,这其中的一些错误能给你的程序造成破坏性的影响,在你尝试使用 Node.js 实现一些很简单的功能时一些错误也可能会导致你受挫。即使 Node.js 已经使得新手上手十分简单,但它依然有些地方容易让人混乱。从其他语言过来的开发者可能已知道了这其中某些错误,但在 Node.js 新手里这些错误都是很常见的。幸运的是,它们都可以很容易地避免。我希望这个简短指南能帮助新手更好地编写 Node.js 代码,而且能够给我们大家开发出健壮高效的软件。

加入我们一起学习吧!


Guess you like

Origin blog.51cto.com/14516511/2433657