写了个轮询检查的工具函数,考虑到递归爆栈的可能,测试了一下。
预料中会爆栈,结果居然能正常运行,惊了
代码如下:
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 ![]() 其实很简单,你看一些主流的 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 AV1 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 异步递归不是递归,实际上连调用栈都没有,或者说调用栈里就那一个函数 |