Javascript递归,内存泄漏?

Javascript recursion, memory leak?

我正在尝试实现每秒执行一次操作的 class,它按预期工作,但我不确定内存泄漏。我正在编写的这段代码将有几个月的正常运行时间。

下面的代码会不会导致内存泄漏,因为它在技术上是永不结束的递归?

class Algorithm{
  constructor(){
    //there will be many more things in this constructor
    //which is why this is a class
    const pidTimer = (r,e=0,y=Date.now()-r) => {
      this.someFunction();
      const now = Date.now();
      const dy = now-y;
      const err = e+r-dy
      const u = err*0.2;
      //console.log(dy)
      setTimeout(()=>{pidTimer(r,err,now)},r+u);
    }
    pidTimer(1000);
  }

  someFunction = () => {}
}

这不是那种在setTimeout()触发并再次调用pidTimer()之前pidTimer()函数调用returns之后有任何堆栈累积的递归。我什至不会调用这个递归(它被安排重复调用),但那更像是一个语义问题。

所以,我看到的唯一可能存在内存泄漏或过度使用的地方是 this.someFunction(); 内部,那只是因为您没有向我们展示那里的代码来评估它并查看它是什么做。您向我们展示的 pidTimer() 代码本身没有问题。

现代异步原语

您当前的功能没有任何“错误”,但我认为它可以得到显着改进。 JavaScript 提供了一个现代化的异步原子,Promise and new syntax support async/await。这些比石器时代的 setTimeoutsetInterval 更受欢迎,因为您可以轻松地通过异步控制流对数据进行线程化,停止考虑“回调”,并避免副作用 -

class Algorithm {
  constructor() {
    ...
    this.runProcess(...)
  }
  async runProcess(...) { // async
    while (true) {        // loop instead of recursion
      await sleep(...)    // sleep some amount of time
      this.someFunction() // do work
      ...                 // adjust timer variables
    }
  }
}

sleep 是一个简单的函数,它在指定的毫秒值后解析一个 promise,ms -

function sleep(ms) {
  return new Promise(r => setTimeout(r, ms)) // promise
}

异步迭代器

但是看看 this.someFunction() 怎么 return 什么都没有?如果我们可以从 someFunction 捕获数据并将其提供给我们的调用者,那就太好了。通过使 runProcess 成为 async generator and implementing Symbol.asyncIterator 我们可以轻松处理异步 停止副作用 -

class Algorithm {
  constructor() {
    ...
    this.data = this.runProcess(...)  // assign this.data
  }
  async *runProcess(...) {            // async generator
    while (true) {
      await sleep(...)    
      yield this.someFunction()       // yield
      ...
    }
  }
  [Symbol.asyncIterator]() {          // iterator
    return this.data
  }
}

现在调用者可以控制数据从 this.someFunction 传入时发生的情况。下面我们写入 console.log,但您可以轻松地将其交换为 API 调用或写入文件系统 -

const foo = new Algorithm(...)
for await (const data of foo)
  console.log("process data", data) // or API call, or write to file system, etc

附加控制

您可以通过使用额外的数据成员轻松地添加对进程的控制。下面我们用条件换出 while(true) 并允许调用者停止进程 -

class Algorithm {
  constructor() {
    ...
  }
  async *runProcess(...) {
    this.running = true       // start
    while (this.running) {    // conditional loop
      ...
    }
  }
  haltProcess() {
    this.running = false      // stop
  }
  ...
}

演示

这是一个包含上述概念的功能演示。注意我们这里只实现 halt 因为 run 是一个 infinite 生成器。有限生成器不需要手动停止。通过 运行 代码段 -

在您自己的浏览器中验证结果

class Algorithm {
  async *run() {
    this.running = true
    while(this.running) {
      await sleep(1000)
      yield this.someFunction()
    }
  }
  halt() {
    this.running = false
  }
  someFunction() {
    return Math.random()
  }
  [Symbol.asyncIterator] = this.run
}

function sleep(ms) {
  return new Promise(r => setTimeout(r, ms))
}

async function main() {
  const foo = new Algorithm          // init
  setTimeout(_ => foo.halt(), 10000) // stop at some point, for demo
  for await (const x of foo)         // iterate
    console.log("data", x)           // log, api call, write fs, etc
  return "done"                      // return something when done
}

main().then(console.log, console.error) // "done"

data 0.3953947360028206
data 0.18754462176783115
data 0.23690422070864803
data 0.11237466374294014
data 0.5123244720637253
data 0.39818889343799635
data 0.08627407687877853
data 0.3861902404922477
data 0.8358471443658225
data 0.2770336562516085
done