Translator: wleonardo

The original link

This post was written by our guest writer Azat Mardan. SitePoint’s guest posts hope to bring you interesting content from prominent authors and speakers in the Web community.

In 2012, I joined Storify and started using Node as my primary language. Since then, I’ve never looked back and thought I missed Python, Ruby, Java or PHP, the languages I’ve used in web development for the last 10 years.

Storify offered me an interesting job because unlike other companies, Storify had (and probably still does) all of its code written in JavaScript. Most companies, especially large ones like PayPal, Walmart, or Capital One, use Node only for a specific purpose. Usually, they use Node as an API or in the business process layer, which is fine. But as a software engineer, there’s nothing better than being completely immersed in the Node environment.

Here are 10 tips that will help you become a better Node developer in 2017. This advice comes from some of the most popular Node and NPM modules that I see and learn from daily development. Here’s what we’ll cover:

  1. Avoid complexity – organize your code to make it as small as possible until it looks too small, and then make it smaller.

  2. Use asynchronous programming – Avoid using synchronous code.

  3. Avoid require blocking – put all of your require declarations at the top of the file, since require is synchronous and blocks code execution.

  4. Know the require cache – this is a feature and a cause of bugs.

  5. Always Check for errors – Errors are not football, never throw an error or skip error checking.

  6. Use try… only in synchronized code. Catch – In asynchronous code try… Catch is useless. V8 engine for try… Catch cannot be optimized.

  7. Return callbacks or use if… Else – Returns a callback just to ensure that execution does not continue.

  8. Listen for Error Events – Almost all Node classes/objects have Event Emitter and broadcast error events. Make sure you listen for them.

  9. Know your NPM — use -s or -d to install modules instead of –save or –save-dev ‘.

  10. Use exact version numbers in package.json: NPM automatically uses the default version numbers when installing modules using -s, which you need to manually change to lock the version numbers. Do not trust SemVer (semantic versioning standard) in your project unless it is an open source module.

  11. Bonus points – Use different dependencies. Put in devDependencies what your project needs during development, using NPM I — Production. The more redundant dependencies you have, the greater the risk of problems.

Ok, let’s look at each of these points individually.

Avoid complexity

Let me take a look at a module written by NPM creator Isaac Z. Schlueter. For example, use-strict, which enforces strict patterns in Javascript, is just three lines of code:

var module = require('module') module.wrapper[0] += '""use strict""; ' Object.freeze(module.wrap)Copy the code

So why should we avoid complexity? A famous phrase that originated in the U.S. Navy :KEEP IT SIMPLE STUPID(or “KEEP IT SIMPLE, STUPID”). That’s why. It turns out that the human brain can only hold five to seven items in its working memory at any one time.

By modularizing your code into smaller pieces, you and other developers will understand it better. You can also test it better. As an example,

app.use(function(req, res, next) {
  if (req.session.admin === true) return next()
  else return next(new Error('Not authorized'))
}, function(req, res, next) {
  req.db = db
  next()
})

Copy the code

Or is it

const auth = require('./middleware/auth.js')
const db = require('./middleware/db.js')(db)

app.use(auth, db)

Copy the code

I’m sure most people will enjoy the second example, especially if the name explains itself. Today, when you write code, you probably think you know how it works. You even want to show how clever you are by linking several features together on the same line. But then you’re writing stupid code. If you’re thinking too hard to write this code, it’s going to be hard to understand when you look at it in the future. Keep your code simple, especially in Node asynchronous code.

Of course there was a left-pad event, but it only affected projects that relied on the left-pad module and the replacement was released 11 minutes later. The benefits of code minimization outweigh its disadvantages. NPM has changed the release strategy, and any important projects should use cached or proprietary sources (as an interim solution).

Use asynchronous programming

Synchronizing code in Node only takes a small amount of time. Most of this code is for command line tools or other scripts that have nothing to do with Web applications. Node developers are mostly writing Web applications, so using asynchronous code can avoid blocking the scene.

For example, when you’re writing a script for a database or a task that doesn’t require control of parallelism, this might work:

let data = fs.readFileSync('./acconts.json')
db.collection('accounts').insert(data, (results))=>{
  fs.writeFileSync('./accountIDs.json', results, ()=>{process.exit(1)})
})

Copy the code

But when you’re creating a Web application, it’s better to write:

app.use('/seed/:name', (req, res) => {
  let data = fs.readFile(`./${req.params.name}.json`, ()=>{
    db.collection(req.params.name).insert(data, (results))=>{
      fs.writeFile(`./${req.params.name}IDs.json`, results, ()={res.status(201).send()})
    })
  })
})

Copy the code

The difference is whether you need to write a concurrent (usually long-running) or non-concurrent (short-running) system. As a rule of thumb, always use asynchronous code in Node.

Avoid require blocking

Node has a simple module loading system that uses the CommonJS module format. It is based on the require function, which makes it easy to introduce modules in different files. Unlike AMD/ RequireJS, Node/CommonJS modules load synchronously. Require works by importing the contents of a module or file export:

`const react = require('react')`

Copy the code

But most developers are not aware that require is cached. As a result, as long as the resolved filename doesn’t change dramatically (such as when the NPM module doesn’t exist), the module’s code will only be executed and stored in the variable once (in the current process). This is a good optimization. Of course, even with caching, you’re better off writing your require declaration at the beginning. The following code is loaded when the AXIos module is actually used in routing. /connect will be slow to load modules when the request is sent.

app.post('/connect', (req, res) => {
  const axios = require('axios')
  axios.post('/api/authorize', req.body.auth)
    .then((response)=>res.send(response))
})

Copy the code

A better, better performance approach is to introduce modules before service definition rather than in routing:

const axios = require('axios')
const express = require('express')
app = express()
app.post('/connect', (req, res) => {
  axios.post('/api/authorize', req.body.auth)
    .then((response)=>res.send(response))
})

Copy the code

You know require is going to be cached

I mentioned in the previous section that require is cached, but interestingly we also have code outside of module.exports. For example:

console.log('I will not be cached and only run once, the first time')

module.exports = () => {
  console.log('I will be cached and will run every time this module is invoked')
}

Copy the code

We learned that some code runs only once, and you can use this feature to optimize your code.

Always check for errors

Node is not Java. In Java, you can throw errors, because if an error occurs then you want the application to stop executing. In Java, you can just use a simple try in the outer layer… Catch can handle multiple errors.

This is not the case in Node. Since Node uses event loops and asynchronous execution, any error that occurs is handled with an error handler (such as try… Catch), the following does not work in Node:

try {
  request.get('/accounts', (error, response)=>{
    data = JSON.parse(response)
  })
} catch(error) {
  // Will NOT be called
  console.error(error)
}

Copy the code

But try… Catch can be used in synchronized code. The previous code snippet could be better refactored as:

request.get('/accounts', (error, response)=>{
  try {
    data = JSON.parse(response)
  } catch(error) {
    // Will be called
    console.error(error)
  }
})

Copy the code

If we can’t wrap the return of the request in a try… Catch, there will be no way to handle the error of the request. The Node developers solved this problem by adding error to the returned argument. Therefore, we need to handle errors manually in every callback. You can check for errors (error is not null) and display the error message to the user or display it on the client and log it, or passing it back up the call stack by calling the callback with error (if you have the callback and another function up the call stack).

request.get('/accounts', (error, response)=>{
  if (error) return console.error(error)
  try {
    data = JSON.parse(response)
  } catch(error) {
    console.error(error)
  }
})

Copy the code

One trick is that you can use the OKAY library. You can use it like the example below to avoid manually checking for errors in callback hell (hello, callback hell).

var ok = require('okay')

request.get('/accounts', ok(console.error, (response)=>{
  try {
    data = JSON.parse(response)
  } catch(error) {
    console.error(error)
  }
}))

Copy the code

Return the callback or use if… else

Nodes are parallel. But this feature can cause bugs if you’re not careful enough. To be safe, use a return to terminate code execution:

let error = true
if (error) return callback(error)
console.log('I will never run - good.')

Copy the code

This prevents something (or an error) from being executed that should not have been executed because the code logic was mishandled.

let error = true if (error) callback(error) console.log('I will run. Not good! ')Copy the code

Be sure to use return to prevent the code from continuing.

Listening to theerrorThe event

Almost all classes/objects in Node have event dispatchers (observer mode) and broadcast error events. This is a nice feature that allows developers to catch these nasty bugs before they have huge consequences.

Make it a good habit to create error event listeners with.on() :

var req = http.request(options, (res) => {
  if (('' + res.statusCode).match(/^2\d\d$/)) {
    // Success, process response
  } else if (('' + res.statusCode).match(/^5\d\d$/))
    // Server error, not the same as req error. Req was ok.
  }
})

req.on('error', (error) => {
  // Can't even make a request: general error, e.g. ECONNRESET, ECONNREFUSED, HPE_INVALID_VERSION
  console.log(error)
})

Copy the code

Know your NPM

Many Node and front-end developers know that using –save when installing modules will save an entry containing the module’s version information in package.json at the same time the module is installed. Of course, there is also –save-dev can be used to install devDependencies(modules that are not needed in the build environment). But do you know if -s and -d can be used instead of –save and –save-dev? The answer is yes.

When you install a module, you need to remove the -s and -d tags that automatically add to your module’s version number. No, when you install a module using NPM Install (or NPM I), the latest image (the second digit of the version number) is automatically pulled. For example, V6.1.0 is a mirror branch of V6.2.0.

The NPM team recommends using Semver, but you’d better not. The NPM team assumed that open source developers would comply with Semver, so they automatically added ^ when installing NPM. No one can guarantee that, so it’s best to lock in your version number. A better approach is to use Shrinkwrap: NPM Shrinkwrap generates a file containing the specific version of the dependency.

conclusion

This article is the first of two, and we’ve covered everything from using Callbacks and asynchronous code to checking for errors and locking dependencies. Hopefully you’ll learn something new, something useful. Stay tuned for part 2 soon.

In the meantime, tell me what you think. Am I missing something? Would you have done it differently? Let me know what you think in the comment section below.