【问题标题】:Memory leak in nodejs promise design?nodejs承诺设计中的内存泄漏?
【发布时间】:2019-02-24 21:26:01
【问题描述】:

我注意到这里有些人建议在您想延迟执行某些事情时直接使用 await/async 和 promise 而不是 setTimeout。这是代码:

async wait (ms){
  return new Promise(resolve => setTimeout(resolve, ms));
}

所以我会使用

await wait(3000);
my_function();

而不是

setTimeout(() => {
  my_function();
}, 3000);

这是有道理的,但我注意到如果我这样做,我会增加内存使用量,并最终导致应用程序在几个小时后崩溃并出现大量内存。

这是 nodejs 的 Promise 设计中的问题,还是我在这里遗漏了什么?


这段代码重现了这个问题:

const heapdump = require('heapdump'),
      fs = require('fs');
class test {
  constructor(){
    this.repeatFunc();
  }
  async wait (ms){
    return new Promise(resolve => setTimeout(resolve, ms));
  }
  async repeatFunc(){ 
    // do some work...
    heapdump.writeSnapshot(__dirname + '/' + Date.now() + '.heapsnapshot');    

    await this.wait(5000); 
    await this.repeatFunc();
  }
}
new test();

注意堆转储每 5 秒增加一次

使用 setInterval 不会发生这种情况:

const heapdump = require('heapdump'),
      fs = require('fs');
class test {
  constructor() {
    setInterval(this.repeatFunc, 5000);
  }
  repeatFunc() { 
    // do some work...
    heapdump.writeSnapshot(__dirname + '/' + Date.now() + '.heapsnapshot');    
  }
}
new test();

【问题讨论】:

  • 请发布应用程序的完整代码(或至少一个重现内存泄漏的minimal reproducible example)。不,promise 不应该自行泄漏内存。你是如何调用等待的函数的?
  • 我发布了重现问题的代码
  • 您的代码很可能由于堆栈溢出而崩溃,来自repeatFunc 的无限递归。与async/await 的承诺无关:-)
  • 也就是说,如果您使用尾递归 (return this.repeatFunc()) 编写代码,那么 a good promise implementation should not leak memory
  • 如果我输入return this.repeatFunc而不是await this.repeatFunc,它仍然会增加

标签: javascript node.js memory-leaks promise async-await


【解决方案1】:

你编写了一个无限递归函数,每个函数调用都会返回一个新的 Promise。每个承诺都在等待从内部解决 - 所以是的,它当然是在积累内存。如果代码是同步的,你会得到一个堆栈溢出异常。

只需使用循环:

const heapdump = require('heapdump'),
      fs = require('fs');

async function wait(ms){
  return new Promise(resolve => setTimeout(resolve, ms));
}
async function repeat() {
  while (true) {
    // do some work...
    heapdump.writeSnapshot(__dirname + '/' + Date.now() + '.heapsnapshot');    

    await wait(5000); 
  }
}

repeat().then(() => console.log("all done"), console.error);

我注意到这里有些人建议在您想延迟执行某事时直接使用await/async 和promise 而不是setTimeout

包括我在内,因为 Promise 更容易使用,尤其是当您想要从异​​步任务返回结果值或处理错误时。但是,如果您不相信 Promise 的任何优点,那么没有什么可以强迫您将已经工作的代码转换为 Promise。继续使用回调样式,直到你找到对 Promise 的好用处。

【讨论】:

  • 或者直接使用setInterval()并完全跳过async/await
  • @jfriend00 但我不能轻易地在setInterval 进程中出现.catch(…) 错误。循环也可能包含其他awaits
  • 那么,您认为所有原本(在承诺之前)使用setInterval() 的情况都应该替换为这种类型的结构?是的,在某些情况下,promise 是所需的结构,但是您或 OP 在这里都没有显示出任何对 Promise 的需求,所以我让 OP 知道还有一个简单的替代方案,没有遇到问题的风险.而且,您甚至没有在示例中显示 .catch()
  • @jfriend00 不,可能不是。然而,OP 使用了递归的 setTimeout 方案,而不是 setInterval,这让我相信 // do some work 本身是异步的。
  • 顺便说一句,我赞成你(不知道为什么有人反对)。我只是认为提及setInterval() 或仅使用setTimeout() 作为安全功能替代方案的无承诺示例也会很有用。
【解决方案2】:

为避免在 Promise 中发生内存泄漏,最好在使用后清除所有超时。

function wait(delay) {
    return new Promise(function(resolve) {
        let id = setTimeout(function() { resolve(); clearTimeout(id);  }, delay);
    });
}

(async () => {
   await wait(2000);
   alert("Test");
})();

【讨论】:

  • setTimeout() 没有内存泄漏。无需在已触发的计时器上调用 clearTimeout()。如果您不这么认为,请提供一些可以证明这一点的参考资料。
猜你喜欢
  • 2022-01-06
  • 2015-01-20
  • 2018-04-28
  • 2016-04-22
  • 1970-01-01
  • 1970-01-01
  • 2013-02-08
  • 2016-07-16
相关资源
最近更新 更多