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 了没?

发表评论