在这篇文章中,我将概述 10 个技巧,以帮助您在 2022 年成为更好的 Node 开发人员。这些技巧来自我,我在战壕中看到并学习了它们,以及编写了最流行的 Node 和 npm 模块的人。以下是我们将介绍的内容:
- 避免复杂性——将你的代码组织成尽可能小的块,直到它们看起来太小,然后让它们变得更小。
- 使用异步代码——避免像瘟疫一样的同步代码。
- 避免阻塞 require – 将所有 require 语句放在文件顶部,因为它们是同步的并且会阻塞执行。
- 知道 require 被缓存了——这可能是你代码中的一个特性或错误。
- 始终检查错误——错误不是足球。永远不要抛出错误,永远不要跳过错误检查。
- 仅在同步代码中使用 try…catch —try…catch对异步代码无用,而且 V8 无法try…catch像普通代码一样优化代码。
- 返回回调或使用 if … else — 可以肯定的是,返回回调以防止继续执行。
- 监听错误事件——几乎所有 Node 类/对象都扩展了事件发射器(观察者模式)并发射error事件。一定要听那个。
- 了解你的 npm-S — 使用or-D代替–saveor安装模块–save-dev
- 在 package.json 中使用精确的版本:npm 默认在你使用时愚蠢地添加了一个插入符号-S,所以手动删除它们以锁定版本。永远不要在您的应用程序中信任 semver,但要在开源模块中这样做。
- 奖励——使用不同的依赖项。将您的项目仅在开发中需要的东西放入devDependencies然后使用npm i –production. 您拥有的非必需依赖项越多,漏洞风险就越大。
因此,让我们一分为二,分别看一下它们中的每一个。我们可以?
不要忘记:如上所述,这是第一部分。您可以在第二部分中找到另外十个技巧。
避免复杂性
看看 npm 的创建者 Isaac Z. Schlueter 编写的一些模块。例如,use-strict对模块强制执行 JavaScript 严格模式,它只有三行代码:
var module = require('module')
module.wrapper[0] += '"use strict";'
Object.freeze(module.wrap)
那么为什么要避免复杂性呢?根据其中一个传说,起源于美国海军的一句著名短语宣称:保持简单愚蠢(或者是“保持简单,愚蠢”?)。这是有原因的。人脑在任何时候只能在其工作记忆中保存五到七个项目。这只是一个事实。
通过将代码模块化为更小的部分,您和其他开发人员可以更好地理解和推理它。你也可以更好地测试它。考虑这个例子,
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)
我相信你们中的大多数人会更喜欢第二个例子,尤其是当名称是不言自明的时候。当然,当您编写代码时,您可能会认为您了解它的工作原理。也许您甚至想通过将几种方法串联在一行中来展示您的聪明程度。请为你的笨拙版本编码。为六个月未查看此代码的您或尝试过或喝醉过的您编写代码。如果你在心智能力的顶峰写代码,那以后你就更难理解了,更不用说你的同事,连算法的复杂性都不熟悉。保持简单对于使用异步方式的 Node 来说尤其如此。
是的,发生了left-pad 事件,但这只影响了依赖公共注册表的项目,并且替换在 11 分钟内发布。做小生意的好处远大于坏处。此外,npm 已更改其取消发布策略,任何严肃的项目都应使用缓存策略或私有注册表(作为临时解决方案)。
使用异步代码
同步代码在 Node.js中确实有一个(小)位置。它主要用于编写 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.js 中编写异步代码。
避免阻塞要求
Node 有一个简单的模块加载系统,它使用 CommonJS 模块格式。它的内置require功能是一种简单的方法来包含存在于单独文件中的模块。与 AMD/requirejs 不同,Node/CommonJS 的模块加载方式是同步的。工作方式require是:您导入在模块或文件中导出的内容。
const react = require('react')
大多数开发人员不知道的require是缓存。因此,只要解析的文件名没有剧烈变化(在 npm 模块的情况下没有),那么模块中的代码将被执行一次并加载到变量中(对于该进程)。这是一个很好的优化。但是,即使使用缓存,最好还是先放置 require 语句。考虑这个代码,它只axios在实际使用它的路由上加载模块。/connect路由会比需要的慢,因为模块导入是在发出请求时发生的:
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')
}
知道某些代码可能只运行一次,您可以使用此功能来发挥自己的优势。
始终检查错误
节点不是Java。在 Java 中,您会抛出错误,因为大多数时候如果出现错误,您不希望应用程序继续运行。在 Java 中,您可以使用单个try…catch.
节点不是这样。由于 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块中,这会给我们留下来自未处理请求的错误。Node 开发人员通过为您提供error回调参数来解决这个问题。因此,您需要始终手动处理error每个回调。您可以通过检查错误(确保不是null)然后将错误消息显示给用户或客户端并记录它,或者通过调用回调将其传递回调用堆栈error(如果您有回调)和调用堆栈上的另一个函数)。
request.get('/accounts', (error, response)=>{
if (error) return console.error(error)
try {
data = JSON.parse(response)
} catch(error) {
console.error(error)
}
})
您可以使用的一个小技巧是OK库。您可以像这样应用它以避免对无数嵌套回调(Hello,callback hell)进行手动错误检查。
var ok = require('okay')
request.get('/accounts', ok(console.error, (response)=>{
try {
data = JSON.parse(response)
} catch(error) {
console.error(error)
}
}))
返回回调或使用 if … else
节点是并发的。因此,如果您不小心,它可能会变成错误。为了安全起见,使用 return 语句终止执行:
let error = true
if (error) return callback(error)
console.log('I will never run - good.')
避免由于控制流处理不当而导致的一些意外并发(和故障)。
let error = true
if (error) callback(error)
console.log('I will run. Not good!')
可以肯定的是,return一个回调以防止继续执行。
收听error事件
几乎所有 Node 类/对象都扩展了事件发射器(观察者模式)并发出error事件。这是开发人员捕捉那些讨厌的错误并在它们造成严重破坏之前处理它们的机会。
error养成使用以下方法创建事件侦听器的好习惯.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)
})
了解你的 npm
许多 Node 和事件前端开发人员都知道–save(for npm install) 不仅会安装模块,还会在模块package.json的版本中创建一个条目。好吧,还有–save-dev, for devDependencies(生产中不需要的东西)。但是你知道你可以用-Sand-D代替–saveand–save-dev吗?是的你可以。
当您处于模块安装模式时,请继续删除那些将为您创建的^标志。它们很危险,因为它们将允许(或其快捷方式)从 npm 中提取最新的次要版本(语义版本控制中的第二位)。例如,v6.1.0 到 v6.2.0 是次要版本。-S-Dnpm installnpm i
npm 团队相信semver,但你不应该。我的意思是,他们之所以使用插入符号^,是因为他们相信开源开发人员不会在次要版本中引入重大更改。任何理智的人都不应该相信它。锁定您的版本。更好的是,使用shrinkwrap:npm shrinkwrap它将创建一个具有依赖项依赖项的确切版本的新文件。
结论
这篇文章是两篇文章中的一部分。我们已经涵盖了很多领域,从使用回调和异步代码,到检查错误和锁定依赖关系。我希望你在这里找到了一些新的或有用的东西。