• The koala
  • The original address: www.toptal.com/nodejs/top-…
  • By MAHMUD RIDWAN

preface

Since its debut, Node.js has received a lot of praise and criticism. This debate will go on and on, and it won’t end any time soon. One of the things that gets lost in the debate is that all languages and platforms are being criticized based on the core question of how we use them. No matter how hard it is to write reliable code in Node.js and how easy it is to write highly concurrent code, the platform has been around for quite some time and has been used to create a large number of robust and complex Web services. These Web services not only have good scalability, but they also prove robust by their duration over the Internet.

However, just like any other platform, Node.js is prone to making mistakes. Some of these errors can degrade application performance, while others can make Node.js unavailable. In this article, we’ll look at 10 common mistakes new to Node.js make, and how to avoid them.

Error 1: Blocking the event loop

JavaScript in Node.js (like a browser) provides a single-threaded environment. This means that your program does not have two things running at the same time, but instead asynchronously handles the concurrency associated with I/O intensive operations. For example, when Node.js makes a request to the database to fetch 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
})
Copy the code

However, in a Node.js instance with thousands of clients connected, a small piece of CPU-intensive code blocks the event loop, causing all clients to wait. Cpu-intensive code includes things like trying to sort a huge array, running a long function, and so on. Such as:

function sortUsersByAge(users) {
	users.sort(function(a, b) {
		return a.age < b.age ? -1 : 1})}Copy the code

Calling the “sortUsersByAge” method on a small “Users” array is fine, but on a large array it can have a huge impact on overall performance. This is fine if you have to do this and you can make sure that no other events are waiting on the event loop (for example, this is just a Node.js command-line tool and it doesn’t care that everything works synchronously). However, it can cause problems when a Node.js server is trying to serve thousands of users simultaneously.

If the Users array is fetched from the database, the ideal solution is to fetch the sorted data from the database. If the event loop is blocked by a loop that computes the historical sum of financial transaction data, the calculation loop should be pushed to a queue outside the event loop so as not to occupy the event loop.

As you can see, there is no silver bullet to solve this type of error, only a separate solution for each situation. The basic idea is not to do CPU-intensive work on Node.js instances that handle concurrent client connections.

Mistake 2: Calling a callback more than once

JavaScript has always relied on callback functions. In browsers, events are handled by passing a reference to the event object to a callback function (usually an anonymous function). In Node.js, callback functions used to be the only way to communicate asynchronously with other code, until promises came along. The callback function is still in use today, and many developers still build their apis around it. A common mistake associated with using callback functions is to call them multiple times. Typically, a method that encapsulates some asynchronous processing is designed to pass a function whose last argument is called after the asynchronous processing:

module.exports.verifyPassword = function(user, password, done) {
	if(typeofpassword ! = = {done (' string ')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)
	})
}
Copy the code

Notice here that every time the “done” method is called, except the last time, there is a return statement. This is because calling the callback function does not automatically end the execution of the current method. If we commented out the first return statement and passed a non-string password to the function, we would still end up calling the computeHash method. Depending on how computeHash is handled in this case, the “done” function is called multiple times. Anyone can get caught off guard when the passed callback function is called multiple times.

Avoiding this problem is just a matter of being careful. Some Node.js developers have therefore made it a habit to prefix all statements that call callback functions with a return keyword:

if(err) {
	return done(err)
}
Copy the code

In many asynchronous functions, this return value is meaningless, so it is simply a way to avoid this error.

Mistake 3: Deeply nested callback functions

Deeply nested callback functions, often referred to as “callback hell,” are not a problem per se, but they can cause code to quickly spiral 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. The login (... .function() {
				done(null, ‘logged in')})})})})}Copy the code

The more complex the task, the worse this is going to be. By nesting callback functions like this, our program is error-prone, and the code is hard to read and maintain. A quick fix is to declare these tasks as small functions and then relate them. However, one of the easiest (and probably easiest) solutions is to use a node.js common component to handle 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. The login (... .function() {
				done(null, ‘logged in')})},function() {
		// ...})}Copy the code

Async.js also provides many methods like “async.waterfall” to handle different asynchronous scenarios. For simplicity’s sake, a simple example is shown here, but the reality is often much more complicated.

(As an advertisement, the introduction of THE ES6 Generator also refers to a Generator that solves callback hell, and uses it more naturally with promises. Please look forward to the next article :D)

Error 4: Expecting the callback function to execute synchronously

Asynchronous programs that use callback functions are not unique to JavaScript and Node.js, but they have made them popular. In other programming languages, we are used to executing two statements one after the other, unless there is a special jump instruction between them. Even then, these are limited to conditional statements, loop statements, and function calls.

In JavaScript, however, a method with a callback function may not complete its task until the callback completes. The current function executes until the end:

function testTimeout() {
	consoleThe log (" Begin ")setTimeout(function() {
		consoleThe log (" Done!" ) }, duration *1000)
	console.log (" Waiting.." )}Copy the code

You may notice that calling “testTimeout” prints “Begin” followed by “Waiting..” “, followed a few seconds later by “Done! .

Any code that is to be executed after the callback has finished executing needs to be called inside the callback.

Error 5: Assigning a value to ‘exports’ instead of’ module.exports’

Node.js considers each file to be a separate module. If your package has two files, let’s say “A.js” and “b.js”, and then “B.js” wants to use “A.js” functions, “A.js” must expose those functions by adding attributes to exports objects:

// a.js
exports.verifyPassword = function(user, password, done) {... }Copy the code

When this is done, all objects requiring “a.js” get an object with the “verifyPassword” function property:

// b.js
require(' a. s')// { verifyPassword: function(user, password, done) { ... }}
Copy the code

However, what if we wanted to expose this function directly, rather than having it as a property of some object? We could override exports to do this, but we must not treat it as a global variable:

// a.js
module.exports = function(user, password, done) {... }Copy the code

Notice that we are referring to “exports” as a property of the Module object. The distinction between “module.exports” and “exports” is an important one, and one that often frustrates newcomers to Node.js.

Error 6: Throwing an error from a callback

JavaScript has a concept of exceptions. Syntactically, like most traditional languages (such as Java and C++), JavaScript can throw exceptions and catch them in try-catch blocks:

function slugifyUsername(username) {
	if(typeofThe username = = = 'string') {throw new TypeError'Expected a string username, got'+(typeof username)) } // ... } try {var usernameSlug = slugifyUsername(username)} catch(e) {console.log(' Oh no! ')}Copy the code

However, in an asynchronous environment, notary catch may not be what you think. For example, if you want to protect a large block of code with a lot of asynchronous processing with a big try-catch, it might not work properly:

try {
	db.User.get(userId, function(err, user) {
		if(err) {
			throw err
		}
		// ...
		usernameSlug = slugifyUsername(user.username)
		// ...})}catch(e) {
	console.log (' Oh no! ')}Copy the code

If the db.user. get callback is executed asynchronously, the scope of the try-catch will have a hard time catching the exception thrown by the callback.

This is why errors are handled differently in Node.js, and it makes all callback arguments follow (err,…). This form, where the first argument is an error object when an error occurs.

Error 7: Thinking Number is an integer data format

In JavaScript, numbers are floating point; there is no data format for integers. You might think this wouldn’t be a problem, since numbers big enough to overflow floating-point limits are rare. In practice, however, something goes wrong when this happens. Because floating-point numbers can only represent an integer up to a maximum value, there will be problems in calculations that exceed this maximum value. It may seem strange, but in Node.js the following code has the value true:

Math.pow(2.53) +1= = =Math.pow(2.53)
Copy the code

Unfortunately, the number quirks in JavaScript don’t stop there. Even though numbers are floating-point, integer operations like the following work:

5 % 2= = =1 // true
5 >> 1= = =2 // true
Copy the code

Unlike arithmetic, however, bitwise and shift operations only work on numbers less than the maximum 32 bits. For example, having “math.pow (2, 53)” shift 1 bit always yields 0, and having it work with 1 always yields 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
Copy the code

You probably won’t have to deal with such large numbers, but there are plenty of large integer libraries that do large, sophisticated number-crunching, such as Node-Bigint, that can help if you need them.

Mistake # 8: Ignoring the benefits of streaming apis

Now we want to create a simple class proxy Web server that responds and initiates requests by pulling the content of other Web servers. As an example, let’s create a small Web server to service a Gravatar image.

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)
Copy the code

In this example, we pull the image from a Gravatar, store it in a Buffer, and then respond to the request. This works fine if the Gravatar image is not too big. But imagine if the size of our proxy content is in the thousands of terabytes. A better way to handle this is as follows:

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)
Copy the code

Here we simply pull the image and pipe it back to the client, rather than having to read the full data into the cache before responding to it.

Error 9: Using console. log for Debug purposes

In Node.js, “console.log” allows you to print anything to the console. For example, if you pass it an object, it will print it out as a JavaScript object character. It can take any number of arguments and print them out with Spaces as delimiters. There are many reasons why developers like to use it to debug their code, but I strongly recommend not using console.log in live code. You should avoid using console.log to debug all code, and you should comment them out when you don’t need them. Instead, you can use a library that does just that, such as Debug.

These libraries provide convenient ways for you to turn specific debug modes on or off when you start a program. For example, with DEBUG, you can block any debug method output to the terminal as long as you don’t set the DEBUG environment variable. Using it is simple:

// app.js
var debug = require(" debug ") (" app ") the debug (" Hello, % s! ", "world")Copy the code

To enable debug mode, simply run the following code to set the environment variable debug to “app” or “*” :

DEBUG=app node app.js
Copy the code

Mistake 10: Not using a monitor

Whether your Node.js code is running in production or your local development environment, a monitor that can coordinate your application is worth having. An oft-mentioned piece of advice for modern programming and development is that your code should have fail-fast. If an unexpected error occurs, don’t try to handle it. Instead, let your program crash and have the monitor restart it within seconds. The benefits of the monitor are not limited to restarting a crashed program. These tools also allow you to restart the program whenever the file changes, just like a crash restart. This makes developing Node.js applications a much more enjoyable experience.

Node.js has too many monitors to use, such as:

pm2

forever

nodemon

supervisor

All of these tools have their pros and cons. Some are good at handling multiple applications on a single machine, while others are good at log management. Anyway, these are great options if you want to start writing a program.

conclusion

As you can see, some of these errors can have a devastating effect on your application, and they can also cause frustration when you try to implement simple functions with Node.js. Even though Node.js has made it easy for beginners to get started, there are still some areas where it can be confusing. Developers from other languages may already be aware of some of these errors, but they are quite common in new Node.js hands. Fortunately, they can all be easily avoided. I hope this brief guide will help novices get better at writing Node.js code and give us all robust and efficient software.

Join us and learn!Node learning exchange group

If the number of ac group reaches 100, you cannot join the group automatically. Please add the wechat id of the group assistant: [coder_qi] Note node, you will be automatically added to the group.