为何Server Actions无法并发执行?附场景示例与优化疑问
给定以下函数:
async function printAB() { console.log("A"); await new Promise((resolve) => setTimeout(resolve, 1000)); console.log("B"); } async function f() { printAB(); printAB(); }
通常调用f()的预期输出为:
A A B B
这是因为console.log('A')语句可与后续的超时操作并发执行。无论函数同时运行在服务端(Server Actions或服务端组件中定义)还是客户端,都能得到该结果。但如果printAB是Server Actions,而f是客户端组件中的函数,输出则变为:
A B A B
请问该现象的原因是什么?同时调用多个此类Actions会导致性能下降,是否有规避方法?
现象原因
Server Actions的本质是客户端向服务端发起的HTTP请求,Next.js等框架为了控制请求负载、保证执行顺序的可预测性,默认会将同一客户端组件发起的多个Server Actions请求串行化处理。
当客户端组件调用Server Actions时,每个printAB()调用都会触发一个独立的服务端请求。框架会让这些请求排队执行:第一个请求执行时,服务端先输出"A",等待1秒后输出"B",请求完成;之后才会处理第二个请求,重复上述流程,最终形成串行的输出结果。
而在纯客户端或纯服务端环境下,函数调用处于同一个事件循环(客户端浏览器或服务端Node.js)中,await只会阻塞当前函数的执行上下文,不会影响其他并发调用的函数,因此两个printAB可以同时启动,先输出两个"A",等待1秒后再输出两个"B"。
规避方法
批量处理请求:将多个需要执行的逻辑合并到一个Server Action中,减少HTTP请求的数量,同时在服务端内部实现并发执行。例如:
// 服务端定义批量处理的Server Action async function printABs(count) { const tasks = []; for (let i = 0; i < count; i++) { tasks.push((async () => { console.log("A"); await new Promise(resolve => setTimeout(resolve, 1000)); console.log("B"); })()); } // 并行执行所有任务 await Promise.all(tasks); } // 客户端组件中调用 async function f() { printABs(2); }这种方式下客户端仅发起一次请求,服务端内部并发处理逻辑,既保证输出符合预期,又能避免多请求串行带来的性能损耗。
优化服务端异步逻辑:如果
await的是数据库查询、API调用等IO操作,在单个Server Action中使用Promise.all并行处理这些操作,避免串行等待,提升整体执行效率。调整框架并发配置:部分框架(如Next.js)提供了取消Server Actions串行限制的选项(如
unstable_noStore),但这种方式可能会导致请求过载,需谨慎使用,优先推荐批量处理方案。
内容的提问来源于stack exchange,提问作者acupoftea

