写了个轮询检查的工具函数,考虑到递归爆栈的可能,测试了一下。
预料中会爆栈,结果居然能正常运行,惊了
代码如下:
function pollingCheck(fn, delay) {
return fn().catch(() => delay().then(() => pollingCheck(fn, delay)))
}
let count = 0
pollingCheck(
() => count++ > 200000 ? Promise.resolve() : Promise.reject(),
() => Promise.resolve() //TODO
).then(() => console.log('finish'))
根据 google 到的最大栈数,最多也就 5 万,下面这段递归轻轻松松就爆栈了
function main(n) {
if (n === 0) {
return 'finish'
} else {
return main(n - 1)
}
}
console.log(main(200000))
所以,为什么上面的 Promsie 递归不会爆栈?
1
kingcc 2018-09-07 09:51:22 +08:00 via Android
我猜因为你 return 的是 Promise instance,下面的是真正的递归
|
2
kingcc 2018-09-07 09:55:48 +08:00 via Android
简单来说就是上面的函数抛出了一个 pending promise,不算是递归因为它执行完了。
|
3
kingwl 2018-09-07 09:56:50 +08:00
没有递归 怎么会爆
|
4
wxsm 2018-09-07 10:19:47 +08:00
实际上你这个是线性执行的吧。如楼上所说,没有递归。
|
5
pofeng OP @kingcc 但是返回的 promise 带了 pollingCheck 内声明的箭头函数,其闭包有引用 fn 和 delay,不会导致 pollingCheck 的运行栈无法释放么?
|
6
zyEros 2018-09-07 10:24:16 +08:00 1
其实很简单,你看一些主流的 Promise 为了实现 Promise 的 lazy 特性的时候,他们都会用到类似于 setTimeout/setImmediate 之类的函数,当然 NativePromise 是直接用的 mirco,例如 q ( https://github.com/kriskowal/q/blob/master/q.js#L150 ),所以当你创建一个 Promise 的时候,他的执行其实是依赖于 setTimeout 的
setTimeout 实际上不会爆,因为 setTimeout 之类的函数依赖的是事件循环,你在 setTimeout 之类注册的函数在 JS Engine 层面可以看做一个对象,setTimeout 的无非只是把这个对象放到了事件循环队列里面等待触发,所以他根本不是递归执行的嘛(逃 |
7
kingcc 2018-09-07 10:30:02 +08:00 via Android
我说了嘛,简单来说…
运行栈等到你执行一次 delay 就释放了一个,你要是还不明白我就画一个图… |
8
zyEros 2018-09-07 10:40:11 +08:00
提供一个例子:
```javascript function x() { new Promise(resolve => { resolve(); x(); }); } x(); ``` |
9
DOLLOR 2018-09-07 10:57:00 +08:00 via Android
递归不一定会爆栈的,比如尾递归
|
10
AnonymousUser 2018-09-07 11:06:27 +08:00
@DOLLOR js 没有尾递归优化,一样爆栈
|
11
zyEros 2018-09-07 11:07:30 +08:00
例子提供错了:
function x() { Promise.resolve().then(x); } x(); 这个其实和你: function x() { setTimeout(x,0); } x(); 效果是一样的 |
12
SakuraKuma 2018-09-07 11:40:20 +08:00
上面都说了,microtask/macrotask 不会卡着主线程
你第二个例子是主线程的。 而且如#9 所说,尾递归的栈帧处理也不会爆掉。 (本人拙见 |
13
pofeng OP |
14
leemove 2018-09-07 12:00:45 +08:00
哇,这些天天争 Vue,React,Angular 的大手,都被一个 bridgePromise 卡住了...还有 Promise 是走异步事件循环的.
|
15
maplerecall 2018-09-07 12:15:03 +08:00 via Android
@AnonymousUser es6 已经有尾递归优化了,js 发展还是很快的
|
16
orangemi 2018-09-07 12:40:03 +08:00
问题是为什么不会爆栈,实际上 Promise.then 是把所有的栈都丢掉了,所以不爆栈。
题主可以尝试使用 new Error().stack 查看左后一次的栈,之前几万次的 stack 都没有了。 nodejs 一个 tick 间只有一个栈,调用 Promise.then 中间的过程中,已经走到了另外一个 tick。 |
17
leemove 2018-09-07 12:46:09 +08:00
@maplerecall js 的尾递归在 V8 上默认是不开启的,在 Node 中也需要对 v8 特殊配置才可以.
|
18
otakustay 2018-09-07 12:53:27 +08:00
异步会清栈,所以 Promise 递归爆不掉
箭头函数产生的是作用域,不是栈,这个要分清 |
19
Sparetire 2018-09-07 13:54:59 +08:00 via Android
其实就是函数调用栈转成了异步任务队列了。。同一时刻的内存是有限的,然而即便是无限地递归,转成任务队列这些内存占用也分散在了无限的时间中。。
|
20
xieranmaya 2018-09-09 18:54:25 +08:00 via Android
异步递归不是递归,实际上连调用栈都没有,或者说调用栈里就那一个函数
|