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

Recursive promises can cause stack overflow?

例如,我发现了一些基于 promises 的 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)})
}

所以setTimeout实际上只会在调用堆栈为空时调用r

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

Will this cause Whosebug?

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

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

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

2.2.4 onFulfilled or onRejected must not be called until the execution context stack contains only platform code. [3.1].

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

“platform code” means engine, environment, and promise implementation code. In practice, this requirement ensures that onFulfilled and onRejected execute asynchronously, after the event loop turn in which then is called, and with a fresh stack. This can be implemented with either a “macro-task” mechanism such as setTimeout or setImmediate, or with a “micro-task” mechanism such as MutationObserver or process.nextTick. Since the promise implementation is considered platform code, it may itself contain a task-scheduling queue or “trampoline” in which the handlers are called.


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

这就是 运行 the ES6 spec 中描述工作的方式:

A Job is an abstract operation that initiates an ECMAScript computation when no other ECMAScript computation is currently in progress. A Job abstract operation may be defined to accept an arbitrary set of job parameters.

Execution of a Job can be initiated only when there is no running execution context and the execution context stack is empty. A PendingJob is a request for the future execution of a Job. A PendingJob is an internal Record whose fields are specified in Table 25. Once execution of a Job is initiated, the Job always executes to completion. No other Job may be initiated until the currently running Job completes. However, the currently running Job or external events may cause the enqueuing of additional PendingJobs that may be initiated sometime after completion of the currently running Job.