返回Promise的函数在resolve中递归调用自身是否会引发栈溢出
结论先行
你的判断基本正确:这段递归写法不会触发栈溢出,但存在明显的设计缺陷,不属于安全的实践写法。
为什么不会栈溢出
要搞懂这个问题,先拆解这段代码的执行逻辑:
- 每次调用
myFunction时,会同步执行new Promise的executor函数:如果条件不满足,就执行resolve(myFunction(myInputValue)),随后return退出当前executor。 - 这里的递归调用
myFunction(myInputValue)虽然写在resolve参数里,但Promise.resolve接收另一个Promise时,不会同步等待内层Promise执行完成,而是会把「等待内层Promise落定、再把结果传递给外层」的逻辑放到微任务队列里,等当前同步执行栈完全清空后才会执行。 - 也就是说,每一层递归执行时,上一层的executor函数已经执行完return了,对应的调用栈帧已经被释放,调用栈深度不会随着递归次数累加——哪怕递归执行几十万次,栈深永远是固定的个位数,自然不会出现栈溢出。
可以做个简单验证:把终止条件设为永远不满足,加个计数器统计递归次数,跑到10万次时打印调用栈,你会发现栈深始终没有增长。
对比普通同步递归:哪怕写了return,只要递归调用发生在当前栈帧退出前,栈深就会持续累加,跑到一定次数必然爆栈:
// 同步递归,哪怕有return,几万次调用就会栈溢出 function syncRec(n) { if (n <= 0) return 1 return syncRec(n - 1) // 调用时当前栈帧还存在,栈深持续+1 }
现有写法的风险
不会栈溢出不代表代码安全,这段写法有三个很实际的问题:
- 冗余开销:
async函数本身就会返回Promise,你在内部额外手动new Promise属于无意义的包装,不仅增加性能开销,还容易因为executor逻辑漏写resolve/reject导致Promise永久挂起。 - 阻塞事件循环:这段递归没有任何等待间隔,每一轮结束后会立刻往微任务队列塞下一轮递归任务,相当于占满了整个微任务队列,所有宏任务(IO回调、定时器、UI渲染等)永远拿不到执行权,程序会直接卡死,和死循环表现一致。
- 无兜底逻辑:没有设置最大重试次数,也没有错误捕获,只要终止条件一直不满足,代码就会永远跑下去;如果中间某一步抛错,还会触发未捕获的Promise异常,在Node环境下会直接导致进程退出。
推荐的安全写法
如果要实现「轮询直到条件满足」的逻辑,直接用async/await循环比递归更直观,也方便加间隔、超时、重试次数等兜底逻辑:
async function myFunction(myInputValue, options = {}) { const { maxRetry = 1000, pollInterval = 50 } = options for (let i = 0; i < maxRetry; i++) { // 执行你的计算逻辑 if (someCondition) return true // 加轮询间隔避免阻塞事件循环 await new Promise(resolve => setTimeout(resolve, pollInterval)) } throw new Error('达到最大重试次数,条件仍未满足') }
内容的提问来源于stack exchange,提问作者Kronos_Rob
相关产品推荐
相关产品推荐

