推荐关注
› Meteor
› JSLint - a JavaScript code quality tool
› jsFiddle
› D3.js
› WebStorm
推荐书目
› JavaScript 权威指南第 5 版
› Closure: The Definitive Guide
pofeng

这段基于 Promise 的递归为什么不会爆栈?求 JS 大拿

  •  1
     
  •   pofeng · Sep 7, 2018 · 7852 views
    This topic created in 2982 days ago, the information mentioned may be changed or developed.

    写了个轮询检查的工具函数,考虑到递归爆栈的可能,测试了一下。

    预料中会爆栈,结果居然能正常运行,惊了

    代码如下:

    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 递归不会爆栈?

    20 replies  •  2018-09-09 18:54:25 +08:00
    kingcc
    kingcc
    Sep 7, 2018 via Android
    1
    我猜因为你 return 的是 Promise instance,下面的是真正的递归
    kingcc
    kingcc
    Sep 7, 2018 via Android
    2
    简单来说就是上面的函数抛出了一个 pending promise,不算是递归因为它执行完了。
    kingwl
    kingwl
    Sep 7, 2018
    3
    没有递归 怎么会爆
    wxsm
    wxsm
    Sep 7, 2018
    4
    实际上你这个是线性执行的吧。如楼上所说,没有递归。
    pofeng
    pofeng
    OP
    Sep 7, 2018
    5
    @kingcc 但是返回的 promise 带了 pollingCheck 内声明的箭头函数,其闭包有引用 fn 和 delay,不会导致 pollingCheck 的运行栈无法释放么?
    zyEros
    zyEros
    Sep 7, 2018❤️1
    6
    其实很简单,你看一些主流的 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 的无非只是把这个对象放到了事件循环队列里面等待触发,所以他根本不是递归执行的嘛(逃
    kingcc
    kingcc
    Sep 7, 2018 via Android
    7
    我说了嘛,简单来说…

    运行栈等到你执行一次 delay 就释放了一个,你要是还不明白我就画一个图…
    zyEros
    zyEros
    Sep 7, 2018
    8
    提供一个例子:
    ```javascript
    function x() {
    new Promise(resolve => {
    resolve();
    x();
    });
    }
    x();
    ```
    AV1
    AV1
    Sep 7, 2018 via Android
    9
    递归不一定会爆栈的,比如尾递归
    AnonymousUser
    AnonymousUser
    Sep 7, 2018
    10
    @DOLLOR js 没有尾递归优化,一样爆栈
    zyEros
    zyEros
    Sep 7, 2018
    11
    例子提供错了:
    function x() {
    Promise.resolve().then(x);
    }
    x();

    这个其实和你:
    function x() {
    setTimeout(x,0);
    }
    x();

    效果是一样的
    SakuraKuma
    SakuraKuma
    Sep 7, 2018
    12
    上面都说了,microtask/macrotask 不会卡着主线程
    你第二个例子是主线程的。
    而且如#9 所说,尾递归的栈帧处理也不会爆掉。

    (本人拙见
    pofeng
    pofeng
    OP
    Sep 7, 2018
    13
    @kingcc @zyEros 大概弄明白了,因为 Promise 会再另外一个 Task 运行的的原因所以不会爆栈,而 pollingCheck 的 scope 会释放,不会形成一个很长的链
    leemove
    leemove
    Sep 7, 2018
    14
    哇,这些天天争 Vue,React,Angular 的大手,都被一个 bridgePromise 卡住了...还有 Promise 是走异步事件循环的.
    maplerecall
    maplerecall
    Sep 7, 2018 via Android
    15
    @AnonymousUser es6 已经有尾递归优化了,js 发展还是很快的
    orangemi
    orangemi
    Sep 7, 2018
    16
    问题是为什么不会爆栈,实际上 Promise.then 是把所有的栈都丢掉了,所以不爆栈。
    题主可以尝试使用 new Error().stack 查看左后一次的栈,之前几万次的 stack 都没有了。
    nodejs 一个 tick 间只有一个栈,调用 Promise.then 中间的过程中,已经走到了另外一个 tick。
    leemove
    leemove
    Sep 7, 2018
    17
    @maplerecall js 的尾递归在 V8 上默认是不开启的,在 Node 中也需要对 v8 特殊配置才可以.
    otakustay
    otakustay
    Sep 7, 2018
    18
    异步会清栈,所以 Promise 递归爆不掉
    箭头函数产生的是作用域,不是栈,这个要分清
    Sparetire
    Sparetire
    Sep 7, 2018 via Android
    19
    其实就是函数调用栈转成了异步任务队列了。。同一时刻的内存是有限的,然而即便是无限地递归,转成任务队列这些内存占用也分散在了无限的时间中。。
    xieranmaya
    xieranmaya
    Sep 9, 2018 via Android
    20
    异步递归不是递归,实际上连调用栈都没有,或者说调用栈里就那一个函数
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Privacy   ·   Solana   ·   1394 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 41ms · UTC 17:05 · PVG 01:05 · LAX 09:05 · JFK 12:05
    ♥ Do have faith in what you're doing.