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

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

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

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

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