《Node.js 设计模式》一书中关于 Node.js 基础知识的 5 个 TIL
这周我开始读《Node.js 设计模式》。我拿到的是第三版,还没花时间研究它和之前的版本有什么变化。前六章涵盖了基础知识,之后才开始深入探讨“设计模式”这个内容,所以这些笔记都来自这本书的前半部分。
1.libuv
反应堆模式
libuv
我经常听说它是一个低级 Node.js 库,但现在我对它的功能有了些了解。正如书中所述:
Libuv 代表 Node.js 的底层 I/O 引擎,可能是 Node.js 构建所基于的最重要的组件。除了抽象底层系统调用之外,Libuv 还实现了反应器模式,从而提供了用于创建事件循环、管理事件队列、运行异步 I/O 操作以及将其他类型的任务加入队列的 API。
反应堆模式与多路分解、事件队列和事件循环一起,是其工作原理的核心——将异步事件送入单个队列,在资源释放时执行它们,然后将它们从事件队列中弹出以调用用户代码给出的回调,这是一种紧密协调的舞蹈。
2.模块设计模式
我对 CommonJS 模块和 ES 模块的区别比较了解。但我很喜欢 CommonJS 中对 5 种模块定义模式的清晰阐述:
- 命名导出:
exports.foo = () => {}
- 导出函数:
module.exports = () => {}
- 导出一个类:
module.exports = class Foo() {}
- 导出实例:
module.exports = new Foo()
类似于单例,除非由于同一模块的多个实例而导致。 - Monkey 修补其他模块(对nock有用)
在 ES 模块中,我很喜欢“只读实时绑定”的解释,对于从未见过它并且一直将模块视为无状态代码块的人来说,这看起来很奇怪:
// counter.js
export let count = 0
export function increment () {
count++
}
// main.js
import { count, increment } from './counter.js'
console.log(count) // prints 0
increment()
console.log(count) // prints 1
count++ // TypeError: Assignment to constant variable!
这种可变模块内部状态模式在Svelte 和 Rich Harris 的作品中很常见,我很欣赏它让代码看起来简洁明了。我不知道这种模式是否存在可扩展性问题,但目前为止,对于 ES 模块开发者来说,它似乎运行良好。
我喜欢的最后一个重要主题是 ESM 和 CJS 互操作问题。ESM
不提供require
或__filename
,__dirname
因此您必须在需要时重建它们:
import { fileURLToPath } from 'url'
import { dirname } from 'path'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
import { createRequire } from 'module'
const require = createRequire(import.meta.url)
截至撰写本文时,ESM 还无法原生导入 JSON,而 CJS 可以。您可以使用require
上面的函数解决这个问题:
import { createRequire } from 'module'
const require = createRequire(import.meta.url)
const data = require('./data.json')
console.log(data)
你知道吗?我不知道!
相关新闻:Node v14.13 将允许从 CJS 模块进行命名导入- 这可能是 ESM 在 Node.js 中“正常工作”的最后一步
3. 释放 Zalgo
在 Node.js 中,API 通常是同步或异步的,但 TIL,你可以设计同时同步和异步的 API :
function createFileReader (filename) {
const listeners = []
inconsistentRead(filename, value => {
listeners.forEach(listener => listener(value))
})
return {
onDataReady: listener => listeners.push(listener)
}
}
这看起来很无辜,除非你将其用作异步然后同步:
const reader1 = createFileReader('data.txt') // async
reader1.onDataReady(data => {
console.log(`First call: ${data}`)
const reader2 = createFileReader('data.txt') // sync
reader2.onDataReady(data => {
console.log(`Second call: ${data}`)
})
})
// only outputs First call - never outputs Second call
这是因为 Node 中的模块缓存使得第一次调用异步,而第二次调用同步。izs在一篇博文中将此称为“发布 Zalgo” 。
您可以通过以下方式将 Zalgo 关在笼子里:
- 对同步 API 使用直接样式函数(而不是延续传递样式)
- 通过仅使用异步 API、使用 CPS 以及使用以下方式延迟同步内存读取,使 I/O 完全异步
process.nextTick()
对于 EventEmitter Observers 也可以采用与回调相同的思路。
当必须以异步方式返回结果时,应该使用回调,而当需要传达某事发生时,应该使用事件。
您可以将观察者模式和回调模式结合起来,例如,使用既能实现更简单、更关键的功能又能实现高级事件的回调的glob
包。.on
关于 ticks 和微任务的说明:
process.nextTick
设置一个微任务,在当前操作之后、任何其他 I/O 之前执行- 而
setImmediate
在所有 I/O 事件都处理完毕后运行。 process.nextTick
执行得更早,但如果花费的时间太长,则有 I/O匮乏的风险。setTimeout(callback, 0)
又落后了一个阶段setImmediate
。
4. 管理异步并限制并发async
使用 Node.js 时,很容易引发竞争条件,并意外启动无限并行执行,导致服务器瘫痪。Async库提供了一些久经考验的实用程序来定义和执行这些问题,特别是那些提供有限并发性的队列。
本书将逐步讲解一个简单的网络蜘蛛程序的四个版本,以阐明需要管理异步进程的动机,并描述在规模化过程中出现的细微问题。说实话,我无法做到面面俱到,我不想直接抄写网络蜘蛛项目的所有版本和讨论内容,因为那占了本书的很大一部分,你只能自己去读这些章节了。
5. 流
我经常说,Streams 是 Node.js 中最好、最难破解的秘密。是时候学习它了。Streams 比全缓冲区更节省内存和 CPU,而且更易于组合。
每个流都是 的一个实例EventEmitter
,可以流式传输二进制数据块或离散对象。Node 提供了4 个基本抽象流类:
Readable
(您可以在流动(推)或暂停(拉)模式下阅读)Writable
- 你可能熟悉res.write()
Node 的http
模块Duplex
:可读可写Transform
:一种特殊的双工流,具有另外两种方法:_transform
和_flush
,用于数据转换PassThrough
:Transform
不进行任何转换的流 - 对于可观察性或实现延迟管道和惰性流模式很有用。
import { PassThrough } from 'stream'
let bytesWritten = 0
const monitor = new PassThrough()
monitor.on('data', (chunk) => {
bytesWritten += chunk.length
})
monitor.on('finish', () => {
console.log(`${bytesWritten} bytes written`)
})
monitor.write('Hello!') monitor.end()
// usage
createReadStream(filename)
.pipe(createGzip())
.pipe(monitor) // passthrough stream!
.pipe(createWriteStream(`${filename}.gz`))
izs 推荐使用minipass,它实现了 PassThrough 流,并具有一些更强大的功能。其他一些有用的流实用程序:
- https://github.com/maxogden/mississippi
- https://www.npmjs.com/package/streamx
- 您可以使用lazystream使流变得懒惰(为流创建代理,因此流实例直到某段代码被消耗时才会出现)。
尽管作者确实建议使用本机stream.pipeline函数来最好地组织管道和错误处理。
文章来源:https://dev.to/swyx/5-tils-about-node-js-fundamentals-from-the-node-js-design-patterns-book-4dh2