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只会在调用栈为空时才会调用。
这是好的解决方案,还是有一些递归调用承诺的标准方法?
这会导致计算器溢出吗?
不,不会。根据承诺规范,.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 完成后的某个时间启动。