Node.js最开始让我觉得比较奇怪的地方,就是它大量使用异步代码。
以前写同步代码的时候,程序基本按着:
第一步
↓
第二步
↓
第三步往下走。
但Node.js里经常看到:
await request()
await readFile()
await queryDatabase()理解了异步机制之后,这些代码就没那么神秘了。
为什么需要异步
假设程序要读一个文件:
读取文件
↓
等磁盘
↓
返回结果如果程序在等的时候什么事都干不了,那服务器资源的利用率就很低。
网络请求也是一样:
请求API
↓
等服务器
↓
收到响应等待可能需要几十毫秒甚至几秒。
Node.js的设计就是要尽量避免这种I/O等待把整个程序卡住。
Promise
现代JavaScript里,Promise是异步编程的基础之一。
比如:
const promise = fetch('/api/users')Promise可以理解成:
一个未来可能会拿到结果的对象。
它有几种状态:
pending
↓
fulfilled
或者
pending
↓
rejected所以可以:
promise
.then(result => {
console.log(result)
})
.catch(error => {
console.error(error)
})async/await
Promise本身没问题。
但异步操作一多:
getUser()
.then(user => {
getOrders(user.id)
.then(orders => {
// ...
})
})代码就越来越难读了。
async/await可以把代码写得跟同步写法更像:
const user = await getUser()
const orders = await getOrders(user.id)但要注意:
async/await并没有把异步变成同步。
它只是改变了代码的写法。
try/catch处理异步错误
比如:
try {
const user = await getUser()
console.log(user)
} catch (error) {
console.error(error)
}这样可以集中处理Promise rejected的情况。
API服务里经常能看到这种结构。
别无脑串行await
下面这段:
const user = await getUser()
const settings = await getSettings()
const notifications = await getNotifications()如果三个请求互相不依赖,那其实没必要等前一个结束再开始下一个。
可以:
const [
user,
settings,
notifications
] = await Promise.all([
getUser(),
getSettings(),
getNotifications()
])这样三个请求可以并行跑。
这在项目里是一个很常见的性能优化点。
Event Loop
Node.js的核心之一就是事件循环。
可以简单理解成:
JavaScript
↓
Event Loop
↓
处理任务
↓
等I/O
↓
继续处理其他任务比如:
console.log('A')
setTimeout(() => {
console.log('B')
}, 0)
console.log('C')输出并不是:
A
B
C而是:
A
C
B因为setTimeout的回调不会立即执行。
它会进到后面的任务流程里。
为什么CPU密集型代码会成为问题
Node.js很适合I/O。
但如果执行:
while (true) {
// 非常重的计算
}那事件循环就被占死了。
其他请求也可能没法及时处理。
所以:
Node.js的优势不是"JavaScript能同时跑很多CPU计算"。
而是:
它非常擅长处理大量异步I/O。
Promise.all也不能乱用
虽然:
Promise.all()很方便,但也不能无限并发。
比如一次请求几万个URL:
await Promise.all(
urls.map(url => fetch(url))
)很可能直接造成:
- 网络连接过多
- 内存上涨
- 对方服务器压力过大
- 本机资源耗尽
这时候应该做并发控制。
比如限制同时执行的任务数量。
我现在对异步代码的理解
异步编程不是为了把代码写得更复杂。
它真正解决的是:
等I/O这种操作不应该把整个程序卡住。
理解了:
Promise
async/await
Event Loop
并发之后,Node.js的很多行为就容易理解了。
尤其是在API服务、爬虫、批量请求这些场景里,正确处理异步操作会直接影响程序的性能和稳定性。
所以对于Node.js开发来说,我觉得:
理解异步模型比记住几个API更重要。