递归promise会导致堆栈溢出吗?

时间:2018-05-19 04:27:32

标签: javascript promise callstack

例如,我发现了一些基于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的标准方法?

1 个答案:

答案 0 :(得分:1)

  

这会导致stackoverflow吗?

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

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

Promises/A+ specification中,第2.2.4节说明了这一点:

  

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

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

  

“平台代码”表示引擎,环境和承诺实现代码。实际上,这个要求确保onFulfilled和onRejected异步执行,然后调用事件循环,然后调用新堆栈。这可以使用诸如setTimeout或setImmediate之类的“宏任务”机制,或者使用诸如MutationObserver或process.nextTick之类的“微任务”机制来实现。由于promise实现被认为是平台代码,它本身可能包含一个任务调度队列或“trampoline”,其中调用处理程序。

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

这就是在the ES6 spec

中描述作业的运行方式
  

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

     

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