递归的 Promise 会导致堆栈溢出吗?

7
例如,我发现一些基于Promise的API库,并且需要使用这个库在一定时间间隔内无限次数地发出API请求(像通常的后端循环)。这些API请求实际上是Promise链。
因此,如果我编写以下函数:
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?


不行 - 请参见 https://jsfiddle.net/1v897bnt/ - CertainPerformance
在我的代码中,我在 Promise 中再次调用了我的函数 p - 这不是和在循环中调用 then 一样吗? - GALIAF95
我是指 r 函数。 - GALIAF95
1个回答

9
这会导致堆栈溢出吗?
不会,根据Promise规范,.then()等待堆栈完全展开,然后在堆栈清除后被调用(本质上是在事件循环的下一个时刻)。所以,.then()已经异步地在当前事件处理完成并且堆栈展开后被调用。您不必使用setTimeout()来避免堆栈积累。
无论您重复多少次,您的第一个代码示例都不会有任何堆栈积累或堆栈溢出。
Promises/A+规范中,第2.2.4节说:
“onFulfilled”或“onRejected”必须在执行上下文堆栈仅包含平台代码时才能调用。[3.1]。”
而“平台代码”在此处3.1中定义:
“平台代码”表示引擎、环境和Promise实现代码。在实践中,此要求确保onFulfilled和onRejected在调用then的事件循环转之后异步执行,并具有新的堆栈。这可以使用“宏任务”机制(例如setTimeout或setImmediate)或使用“微任务”机制(例如MutationObserver或process.nextTick)实现。由于Promise实现被认为是平台代码,因此它本身可能包含任务调度队列或“跳板”,其中处理程序被调用。
ES6 Promise规范使用不同的单词,但产生相同的效果。在ES6中,promise .then()通过排队作业来执行,然后让该作业得到处理,只有在没有其他代码正在运行且堆栈为空时才会处理该作业。
ES6规范中描述了这样的作业运行方式:
作业是启动ECMAScript计算的抽象操作,当没有其他ECMAScript计算正在进行时,将启动作业。可以定义一个接受任意一组作业参数的作业抽象操作。
只有在没有运行的执行上下文并且执行上下文堆栈为空时,才能启动作业的执行。PendingJob是对未来执行作业的请求。PendingJob是一个内部记录,其字段在表25中指定。一旦启动作业的执行,该作业始终执行到完成。在当前运行的作业完成之前,不能启动其他作业。但是,当前运行的作业或外部事件可能会导致排队更多的PendingJobs,这些作业可能会在当前运行的作业完成后的某个时候启动。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接