NodeJS 新特性 AbortController

NodeJS 15.0.0
版本增加了一个很有意思的新特性 AbortController, 主要是用来撤销某些正在运行的 Promise
.
AbortController 最早是浏览器支持的一个 API, 主要是用来终止 Web 请求. 扩展到 NodeJS 中就是用来撤销一些异步 IO 操作或者 timer, 提高资源利用率, 相比之下服务端是集中化的资源, 更需要此 API.
基本用法⌗
AbortController
是一个全局类, 可以直接使用.
abortController.signal
是一个 EventTarget
用来通知 abort 事件, 同时我们也可以使用 ac.signal.aborted
来判断一个 controller 是否已经取消, 调用 abortController.abort()
方法会触发 abort 事件, 并且 signal 状态也会切换.
在实际使用时, 都是将 signal
作为额外参数传入支持此功能的 API 方法中, 例如下面是一个撤销 timer 的例子:
上面代码会在 2s
时结束, timersPromises.setTimeout
会 throw 一个 AbortError: The operation was aborted
错误.
API 现状⌗
目前只有部分 API 支持此功能, 可以在 nodejs/node 官方仓库搜索此特性相关提交.
稍微列举一下:
- fs/fsPromises 部分 API
- timers/promises
- http/http2 client
- child_process
- stream
- dgram
实现原理⌗
AbortController
源码可以看 abort_controller.js, 也可以看这个实现 mysticatea/abort-controller. 实现非常简单而且上面也大概提到了, 所以这里不在赘述. 着重介绍标准库如何集成支持 AbortController.
1. timersPromises.setTimeout⌗
上面代码删减了部分不相关代码, 总结下来实现需要 5 步:
- 校验 signal 是否合法
- 执行操作前, 检查 signal 是否已经是 aborted 状态, 如果是直接 throw AbortError
- 执行真正操作(异步)
- 将终止方法绑定在 abort 事件上面, abort 触发时也会 throw AbortError
- 清理 EventListener
2. fsPromises.readFile⌗
上面代码删减了不相关代码, fsPromises.readFile
核心通过 readFileHandle
方法实现, 总结一下就是带缓冲的 read, 每次异步 read chunk 直到文件读完, 最后将内容返回. 因此更加简单, 总结下来只需要两步:
- 执行操作前, 检查 signal 是否已经是 aborted 状态, 如果是直接 throw AbortError
- 同步循环每次操作前检查 signal 是否已经是 aborted 状态
整个总结下来, 其实实现原理都和上面两种方式一样, 也就是: 异步监听事件, 同步循环在循环间检查状态
.
对比 Go 语言⌗
熟悉 go 语言的朋友看到这个肯定会想到 context
, context 的一个很大的作用就是在不同 Goroutine 之间同步取消信号来减少资源浪费. 本文所讲的 AbortController 相当于 context.WithCancel
, 当然 go 语言的 context 功能更加复杂, 是一个树形结构, 但是在用法上思想其实是相通的.
总结⌗
服务端资源是中心化, 所以我们在写代码时应该考虑资源利用情况, AbortController
填补了 NodeJS 在这方面的不足. 想象一下在实现网关时我们会有很多并发聚合 http 调用, 一个请求出错时就可以撤销其他的请求了.
对比 go 语言, context 已经非常成熟, 基本已经是 io 操作函数的第一个参数标配了, 期待 AbortController 在 NodeJS 社区广泛支持的这一天早点到来.