Node 接口该写同步还是异步? 这道题很多人会凭感觉选只要写过一段时间 Node基本都纠结过这个问题。有的人很怕出事干脆把接口里能写成异步的全写成异步觉得这样最稳。也有人图省事能同步就同步反正本地跑得过先把功能交掉再说。这两种写法我都写过而且都出过问题。前一种的问题是代码看起来全是async/await但很多地方根本没有等待外部资源纯粹是在把简单逻辑写复杂。后面一层套一层异常处理、函数签名、调用链都跟着变重。后一种更直接问题通常出在线上。单人联调、本地自测都没事一到并发场景接口开始卡RT 变高前面的请求还没处理完后面的请求已经排上队了。所以这里不打算争“同步好”还是“异步好”。我这里只把这件事说清楚Node 接口里同步和异步到底该怎么判断才不容易选偏。先别急着下结论先把 Node 的运行方式摆出来很多人讨论同步和异步第一反应还是停留在语法层面。比如同步是不是更直白异步是不是更高级async/await是不是默认就比同步安全但这些都不是重点。Node 这件事说到底绕不开事件循环。你可以不背底层细节但有一条得记住主线程一旦被同步逻辑卡住别的请求就得一起等。这也是为什么同样一段“能跑”的代码放在脚本里可能没事放到线上接口里就完全是另一回事。比如下面这种写法const fs require(node:fs); app.get(/profile, (req, res) { const text fs.readFileSync(./profile.json, utf8); res.send(JSON.parse(text)); });单看功能没毛病。问题在于只要这个接口有人在调readFileSync就会把主线程占住。文件没读完这次请求不会往下走别的请求也很难舒服地进来。再看异步版本const fs require(node:fs/promises); app.get(/profile, async (req, res) { const text await fs.readFile(./profile.json, utf8); res.send(JSON.parse(text)); });这里真正值钱的不是多了async这几个字符而是文件读取这件事不会一直堵着主线程。所以同步和异步的分界线从来都不是“哪种写法看起来更现代”而是这段逻辑会不会把当前这条执行线占死。同步不是不能用但别把它塞进不该待的地方我不认同“Node 里同步一律有罪”这种说法。同步当然能用只是它能活动的范围比很多人想得小。1. 服务启动阶段用同步很正常比如读取本地配置、加载一份静态字典、初始化模板。这类事情的特点很明确只做一次而且通常发生在服务真正开始接请求之前。const fs require(node:fs); const config JSON.parse( fs.readFileSync(./config.json, utf8) );这种时候你非要绕成异步收益未必有多大代码反而更散。2. 单机脚本、内部工具同步往往更顺手如果这是个数据迁移脚本或者一个批量处理文件的小工具那同步完全可以接受。因为这个场景根本不在乎“还能不能同时服务别的请求”。它的目标就是把这件事按顺序做完。3. 轻量的纯内存逻辑本来就该同步像字段整理、对象映射、少量判断这种逻辑没有 I/O没有网络等待本来就是同步语义。function toUserCard(user) { return { id: user.id, nickname: user.nickname, isVip: user.level 3, }; }这种函数你硬写成异步没有任何实际意义。但这里有个边界要说清楚。纯内存逻辑不等于永远安全。如果你在请求链路里做的是大循环、大 JSON 处理、大量排序聚合它就算不访问数据库也一样会把主线程拖住。那时候问题已经不是“同步能不能用”而是“这段计算本来就不该这么放”。异步真正该上的地方其实没什么悬念只要你是在写正式的线上服务下面这些地方基本就别犹豫了。1. 数据库操作查库、写库、事务全都属于典型 I/O。app.get(/orders, async (req, res) { const data await orderService.list(req.user.id); res.json(data); });这里如果你还想着同步写法本质上就是拿接口吞吐去换一时省事。2. Redis、MQ、第三方 HTTP 调用只要要等网络结果就老实异步。这里没什么讨论空间。3. 文件读写很多人会下意识觉得本地文件没那么夸张。但对 Node 来说文件 I/O 还是 I/O。只要放在请求主路径上它就有资格把主线程拖慢。4. 用户接口里的耗时操作这一条比前面更实用。你不一定每次都能很快判断某个操作归类到哪里但你可以先问一句这段逻辑是不是在用户请求过程中执行而且会不会花时间。只要答案是“会”就该优先往异步、非阻塞那边靠。真正容易把人带偏的不是同步是“异步绝对正确”我后来越来越觉得Node 新手最容易掉进去的坑其实不是不会写异步而是把异步想成了默认答案。误区 1只要是接口代码就必须全异步这是最常见的误区。接口确实应该避免阻塞但这不等于接口里的每一层逻辑都必须异步。参数清洗、结果拼装、轻量校验这些本来就是同步动作。你非要给它们套一层async本质上只是把很短的一段路绕远了。误区 2同步写法一定更差这句话也太粗。在一次性脚本、启动阶段、低请求量的本地任务里同步不仅不一定差有时候还更省心。Node 里要警惕的从来不是“同步”这两个字本身而是同步阻塞出现在并发请求链路里。误区 3async/await写满就代表工程更规范这几年很容易被带偏的一点就是把写法外观当成工程质量。比如下面这种函数async function buildUserView(user) { return { id: user.id, nickname: user.nickname, }; }它不是错只是没必要。没有异步源却要假装异步这种代码写多了调用方和维护的人都累。如果你真要在项目里做判断我建议先看这 4 件事别再靠感觉选了。感觉最不稳定。我自己现在看 Node 接口里的同步异步基本先过这四个问题。1. 有没有等待外部资源数据库、Redis、文件、HTTP只要涉及等待外部返回优先异步。2. 它在不在请求主路径上如果它就在用户接口的执行链路里那你得天然对阻塞更敏感。3. 它一旦慢下来会不会连带拖住别的请求这点是 Node 和很多后端语言讨论习惯不太一样的地方。你不能只看“它自己能不能跑完”还得看“它跑的时候会不会把别人一起堵住”。4. 它属于启动期还是运行期启动期任务可以放宽运行期任务要保守。很多争论说到底就是把这两个阶段混成了一件事。最后是总结的一份的场景判断如果你平时开发节奏快不想每次都重新分析那可以先按这个表判断场景建议服务启动时读取本地配置同步即可单机脚本、构建脚本、内部工具同步优先轻量对象映射、字段整理、简单计算保持同步数据库 CRUD必须异步Redis / MQ / 第三方接口调用必须异步文件上传、下载、读写必须异步面向用户的线上接口主路径优先非阻塞设计这张表不是死规定但拿来挡掉大部分误判够用了。写到最后我自己的结论其实很简单Node 里没有“同步永远落后”也没有“异步天然正确”。同步适合一次性、短链路、轻逻辑。异步适合 I/O、等待态、并发请求场景。真正该盯住的不是语法样子而是这段代码会不会影响整条请求链路的流动性。最后总结成一句话就是先判断这段代码会不会堵住别人再决定它该不该写成同步。你们平时写 Node 接口最常见的误判是哪一种是把同步放到了不该放的地方还是把async/await用成了默认装饰

本月热点