Node.js 的孤独:我们都在用异步,却忘了它为什么存在
这年头写 Node.js 的人越来越多,但真正理解它当初为什么诞生的人,反而越来越少。我见过太多团队把 async/await 用成了一种条件反射,遇事不决就 await,好像只要加了它,性能就能起飞。但问题是,你用 Node.js 写接口,是看中了它的高并发,还是只是因为它 npm 装包方便?如果你连它跟前端那套事件循环的继承关系都懒得去捋,那踩坑只是时间问题。
我印象最深的一次,是给一个物流系统做重构。老代码用 Java,换 Node.js 的理由听起来很体面——减少服务器成本。但真正跑起来才发现,团队里的同学把数据库查询、文件处理、甚至一个简单的字符串拼接都写成异步函数,满屏的 Promise 和 await 看起来像天书。后来压测一跑,QPS 不但没涨,反而因为线程池和 libuv 的切换开销降了三成。那会儿我才醒过味儿:异步不是免费的午餐,它是一个需要你自己管理复杂性的游戏。你可以在 IO 密集的场景里占到便宜,但如果你把它用到 CPU 密集的业务逻辑上,那就是拿自己的短板去硬刚别人的长板。
很多人喜欢把 Node.js 和 Go 放在一起比,我不太爱干这事儿。Go 的 goroutine 是语言层面的东西,你不用想太多,扔个 go 关键字就完了。Node.js 呢?你写的是 JavaScript,但底层的事件循环、nextTick、微任务宏任务的区分,这些概念你逃不掉。而且特别讽刺的是,我们费劲心思用回调函数、Promise、async/await 去解决异步地狱,到头来在 Node 16 之后,又冒出来个 node:test 自带的运行器——它竟然默认就是同步的。这让我想起一个老笑话:我们用了十年时间逃离回调,结果官方帮你把回调请回来了,只是换了个马甲叫测试函数。
说实话,我现在已经不再劝人用什么框架、什么工具了。如果你真的要选 Node.js,我希望你是因为看过它的源码,或者至少读过 event loop 的文档,而不是因为某个技术博主说它“轻量、快速、生态好”。这个圈子太喜欢造神了,今天说 Deno 替代 Node,明天说 Bun 秒天秒地,可事实上,没有哪个运行时能帮你解决业务复杂度。真正的取舍永远在你的脑子里:什么时候并发,什么时候排队,什么时候把任务丢给另一个进程——这些战场的决策,才是你写代码时最该小心翼翼的地方。
所以你看,Node.js 就像那个总被误解的孤独天才。它给了你一个很酷的模型,却从没告诉你该怎么驾驭它。而我写了这么多年代码,最后悔的就是当初没有早点丢掉那些“最佳实践”,而是自己把 event loop 每一层扒开来看。现在我把这篇文章写出来,不是为了嘲笑谁,只是想跟你说:你要是真的想用它,就别只学语法,去琢磨它为什么存在。否则,你迟早会在一个深夜的线上事故里,对着 CPU 飙升的监控图,想起我今天说的这些话。