递归承诺会导致堆栈溢出吗?

GAL*_*F95 5 javascript callstack promise

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

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

function r(){
    return api
        .call(api.anotherCall)
        .then(api.anotherCall)
        .then(api.anotherCall)
        ...
        .then(r)
}
Run Code Online (Sandbox Code Playgroud)

会导致堆栈溢出吗?

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

function r(){
    return api
        .call(api.anotherCall)
        .then(api.anotherCall)
        .then(api.anotherCall)
        .then(()=>{setTimeout(r, 0)})
}
Run Code Online (Sandbox Code Playgroud)

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

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

jfr*_*d00 6

这会导致计算器溢出吗?

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

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

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

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

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

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


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

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

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

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