首页 > 解决方案 > 递归承诺会导致堆栈溢出?

问题描述

例如,我发现了一些基于 Promise 的 api 库,我需要在某个时间间隔、无限次(如通常的后端循环)中使用该库发出 api 请求。这个 api 请求 - 实际上是一系列的承诺。

所以,如果我写这样的函数:

function r(){
    return api
        .call(api.anotherCall)
        .then(api.anotherCall)
        .then(api.anotherCall)
        ...
        .then(r)
}

会不会导致栈溢出?

我想出的解决方案是使用 setTimeout 进行r递归调用。

function r(){
    return api
        .call(api.anotherCall)
        .then(api.anotherCall)
        .then(api.anotherCall)
        .then(()=>{setTimeout(r, 0)})
}

所以 setTimeoutr只有在调用堆栈为空时才会调用。

这是一个好的解决方案,还是有一些递归调用承诺的标准方法?

标签: javascriptpromisecallstack

解决方案


这会导致stackoverflow吗?

不,它不会。根据 Promise 规范,.then()等待堆栈完全展开,然后在堆栈清除后调用(基本上是在事件循环的下一个滴答声中)。因此,.then()在当前事件完成处理并且堆栈展开后,已经异步调用。您不必使用setTimeout()来避免堆栈堆积。

无论您重复多少次,您的第一个代码示例都不会有任何堆栈堆积或堆栈溢出。

Promises/A+ 规范中,第 2.2.4 节这样说:

2.2.4 在执行上下文堆栈只包含平台代码之前,不得调用 onFulfilled 或 onRejected。[3.1]。

并且,“平台代码”在 3.1 中定义:

“平台代码”是指引擎、环境和承诺实现代码。在实践中,此要求确保 onFulfilled 和 onRejected 在调用 then 的事件循环之后异步执行,并使用新堆栈。这可以通过 setTimeout 或 setImmediate 等“宏任务”机制实现,也可以通过 MutationObserver 或 process.nextTick 等“微任务”机制实现。由于 Promise 实现被认为是平台代码,它本身可能包含一个任务调度队列或调用处理程序的“蹦床”。


ES6 Promise 规范使用不同的词,但产生相同的效果。在 ES6 中,promise.then()是通过将作业加入队列然后让该作业得到处理来执行的,并且只有在没有其他代码运行并且堆栈为空时才处理该作业。

这就是ES6 规范中描述的诸如作业之类的运行方式:

Job 是一个抽象操作,当当前没有其他 ECMAScript 计算正在进行时,它会启动 ECMAScript 计算。作业抽象操作可以定义为接受任意一组作业参数。

只有在没有正在运行的执行上下文且执行上下文堆栈为空时,才能启动 Job 的执行。PendingJob 是对未来执行 Job 的请求。PendingJob 是一个内部记录,其字段在表 25 中指定。一旦启动 Job 的执行,Job 总是执行到完成。在当前运行的作业完成之前,不得启动其他作业。但是,当前运行的 Job 或外部事件可能会导致额外的 PendingJobs 排队,这些 PendingJobs 可能会在当前运行的 Job 完成后的某个时间启动。


推荐阅读