Promise

Promise

Promise是ES6的新特性,在ES6之前各大浏览器、各种polyfill和各种js执行环境都针对Promise进行了自己的实现,不过实现上大同小异。

 

V8 Promise内存泄漏

不过V8对Promise的实现存在内存泄漏问题,当一个promise无法resolve也无法reject的时候,就会发生内存泄漏。

一个很容易造成Promise内存泄漏的场景便是递归Promise或者嵌套Promise。

为方便观察内存使用情况,下面是一段在Nodejs里面运行的代码

在我的电脑上输出:

关键代码就是这一行

嵌套的Prosise形成了一个Promise状态链,外层Promise等待内层Promise调用resolve或者reject,因为V8原生的Promise实现存在缺陷,这样使用之后会积累一大坨Promise,无法被释放,所以就造成了内存泄漏。

有趣的是,将代码稍作修改,将第5行的return去掉,却不会出现内存泄漏的问题

输出:

究其原因是没构成嵌套Promise,run返回了undefined,不是一个Promise。

为了证明V8的实现确实存在问题,我们来看看bluebird的实现,bluebird是众多Promise polyfill中的一个。

使用bluebird来解决这个问题非常简单,只用将Promise替换成bluebird的实现,同时为每个Promise调用done,关于为什么Prosime需要done,请看这里

done的作用是告诉引擎没有任何地方需要等Promise resolve或者reject了,可以gc掉了,因为状态已转移到内层Promise去了,只用保留最内层Promise即可。

到目前为止V8引擎还没实现原生的Promise.done。

 

总结一下

  • 随着ES6/ES7的快速发展,Promise显得日益重要,今后将会有大量基于Promise的API出现
  • 嵌套Promise应该属于反模式了吧,尽量避免写出这种代码
  • 确保每一个Promise都会调用done,引擎不存在这种缺陷的情况除外
  • 相信Promise会越来越好用,各个js引擎也会对Promise进行优化
  •  

以上纯属基于简单技术模型的研究,实际中遇到Promise内存泄漏往往很不好排查问题,如发现文章中有不足之处还望在留言中指出。

原创文章转载请注明:

转载自AlloyTeam:http://www.alloyteam.com/2015/05/memory-leak-caused-by-promise/

  1. xuzicn 2016 年 6 月 24 日

    你这个说法是错误的。用得多不代表泄露,内存泄露的定义有三个要点:1)不再使用,2)失去控制,3)未能释放事实上你在第一段代码的run()后加一个then(() => { global.gc() }),内存就迅速的恢复了

  2. 钉子 2016 年 5 月 3 日

    return一个Promise不就是为了形成Promise chain顺序执行吗?作者后面举的例子直接调用.done()相当于根本没有等待上一个promise的Fulfilled/Rejected,直接执行了,这不叫解决问题的方法吧?

  3. ADoyle 2015 年 11 月 4 日

    请问 bluebird 解决这个问题的原理是什么,然后 bluebird 官方似乎不推荐使用 done 这个方法,把它归为历史原因了。
    参见 http://bluebirdjs.com/docs/api/done.html

  4. hussion 2015 年 6 月 19 日

    第一部分的代码给出有误。明明写的是console.log(i);怎么会打印出memoryUsage呢?还有既然是在node环境下,直接用setImmediate不就行了嘛,还用setTimeout为0的timer干啥呢?

  5. 这TM能看部门? 2015 年 5 月 30 日

    标题取个”V8引擎Promise实现缺陷 – 内存泄漏”不更好,另外给V8发issue了没?

发表评论