我会在这篇文章中简述 10 个技巧,帮助你在 2017 年成为更好的 Node 开发者。这些技巧有一些是我在实践中学习和领悟的,还有一些从优秀的 Node 和 npm 模块作者那里借鉴而来。本文的内容包含如下一些:
我们应该分别看待上述每一条,不是吗? 避免过于复杂Isaac Z. Schlueter,是 npm 的创造者,我们来看看他写的代码。例如,用 use-strict 在模块中强制实施 JavaScript 严格模式,只需要三行代码: var module = require('module')
module.wrapper[0] += '"use strict";'
Object.freeze(module.wrap) 为什么要避免复杂性呢?美国海军传说中,有一句短语很著名:化繁为简,返璞归真。(或者“保持简单,蠢货!”)。因为事实证明,人的大脑在同一时刻只能记忆 5 到 7 项内容。 把代码模块化为更小的部分,你和其它开发者才能更好的理解它。这样你也可以更好的测试它。看看这个例子: 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()
}) 或者这个: const auth = require('./middleware/auth.js')
const db = require('./middleware/db.js')(db)
app.use(auth, db) 我相信大多数人会更喜欢第2个例子,尤其是在名称可以自解释的时候。当然,如果你在写代码,你会想知道它的工作方式。有时,你可能会耍耍小聪明,将多个方法在同一行里进行链式调用。不过,请按最简单的方式编写代码。因为在时隔 6 个月,或在你喝醉或极度兴奋的时候,再来看你写的这些代码,可能你自己都难以理解,更不用说那些不了解它的算法和复杂性的同事了。简单做事,尤其是在使用 Node 的异步方式的时候。 有一种 left-pad 事件,不过它只影响了依赖公共注册表的项目,并在 11 分钟之后重新发布。最小化所带来的益处远大于其缺点。而且,npm 已经修改了它的发布策略,任何重要的项目都应该使用缓存或私有注册中心(作为临时解决方案)。 使用异步代码同步代码确实在 Node 中有一个(低的)位置。 它主要用于编写 CLI 命令或与 Web 应用程序无关的其他脚本。Node 开发者主要构建 Web 应用程序,因此他们使用异步代码,以避免阻塞线程。 let data = fs.readFileSync('./acconts.json')
db.collection('accounts').insert(data, (results))=>{
fs.writeFileSync('./accountIDs.json', results, ()=>{process.exit(1)})
}) 但是在构建 Web 应用时下面写法会更佳: 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()})
})
})
}) 区别在于你是否在写并发(通常是长时间运行)还是非并发(短时间运行)的系统。 根据经验,我们总是在 Node 中使用异步代码。 避免阻塞请求Node 有一个简单的模块加载系统,使用 CommonJS 模块规范。它内建的 require 函数很容易把另外单独存放的的模块包含进来。与 AMD/requirejs 不同,Node/CommonJS 采用同步的方式加载模块。require工作方式是:导入在模块或文件中导出的内容。 const react = require('react') 大多数开发知道 require 有缓存。因此,只要解析出来的文件名没什么变化(在 npm 模块中是没有的),模块中的代码会只执行一次并将结果保存在一个变量中(同一进程内)。这个优化非常棒。然而,在有缓存的情况下,你仍然最好把 require 语句放在前面。看看下面的代码,在真正进入路由的时候才加载 axios 模块。/connect 路由出乎预料的慢,因为它在导入模块的时候才开始请求文件[译者注:IO 操作比 CPU 运算慢很多]: app.post('/connect', (req, res) => {
const axios = require('axios')
axios.post('/api/authorize', req.body.auth)
.then((response)=>res.send(response))
}) 更好更高效的方式是服务器启动后就加载模块,而不是在路由中: 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))
}) 了解 require 会被缓存我在上一节提到过 require 缓存,但有趣的是,我们可以有 module.exports 之外的代码。例如, 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')
} 知道一些代码可能仅运行一次,你可以使用这种此功能作为优势。 总是检查错误Node 不是 Java。在 Java 中,你可以抛出错误,因为多数时候你会在这些错误发生时中止应用程序的执行。在 Java 中,你可以通过一个单独的 try ... catch 处理多个错误。 在 Node 中不是这样。Node 使用事件循环和异步执行,错误发生时会不属于与处理代码(比如 try...catch)不同的上下文中。下面的作法在 Node 中无效: try {
request.get('/accounts', (error, response)=>{
data = JSON.parse(response)
})
} catch(error) {
// Will NOT be called
console.error(error)
} 不过 try...catch 仍然可以用于同步的 Node 代码。对上面的代码进行重构之后就好多了: request.get('/accounts', (error, response)=>{
try {
data = JSON.parse(response)
} catch(error) {
// Will be called
console.error(error)
}
}) 如果我们不能将 request 调用放在 try...catch 块中,我们就不能处理来自 request 的错误。Node 开发者采用提供包含 error 参数的回调来解决这个问题。这样你需要在每个回调中手工处理错误。你需要检查 error(确保它不是 null),然后将相应的错误消息显示给用户或者客户端,并记录下来。也可以通过调用栈中的 callback 往回传(如果你有回调,而且调用栈上还有另一个函数)。 request.get('/accounts', (error, response)=>{
if (error) return console.error(error)
try {
data = JSON.parse(response)
} catch(error) {
console.error(error)
}
}) 你还可以使用 okay 库。你可以像下面的代码那样使用它来避免在无数的回调中手工检查错误(你好,回调地狱)。 var ok = require('okay')
request.get('/accounts', ok(console.error, (response)=>{
try {
data = JSON.parse(response)
} catch(error) {
console.error(error)
}
})) 返回回调或使用 if … elseNode 是并发的。所以,如果不加注意,它可能会变成一个错误。 为了安全起见,我们使用 return 语句终止执行: let error = true
if (error) return callback(error)
console.log('I will never run - good.') 确保返回一个回调,以防止继续执行。 监听错误事件几乎所有的 Node 类/对象都扩展了事件发射器(观察者模式)并抛出错误事件。在错位被破坏之前,这给开发人员提供了捕获错误并处理的机会。 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)
}) 了解 npm很多 Node 开发者甚至前端开发者都知道 --save(npm install 的参数)可以安装一个模块并在 package.json 中记录模块的版本。另外,还有 --save-dev,用于在 devDependencies 添加记录(记录那些不需要在发布时的模块)。不过你知道可以用 -S 和 -D 代替 --save 和 --save-dev 吗?你可以尝试这样做。 在 package.json 中使用准确的版本号在安装模块的时候,去删除 -S 和 -D 为你添加的那些 ^ 记号。它们非常危险,因为它们允许 npm install(或简写为 npm i)从 npm 库中拉取最新的小版本(语义化的版本号中的第2个数)。比如从 v6.1.0 到 v6.2.0 就是一个小版本发布。 npm 团队信任 semver,但你不能。他们加上 ^ 符号是因为他们相信开源作者不会在小版本中引入破坏性的修改。然而明眼人都知道它是不可靠的。你应该锁定版本号,最好使用 shrinkwrap:npm shrinkwrap 创建一个包含依赖的具体版本的新文件。 结语以上是文章的第一部分。 我们已经涵盖了很多东西,从使用回调函数和异步代码,到检查错误和锁定依赖。 我希望你在这里找到了一些新的或有用的东西。 |