在许多 promise 链之间共享一个全局已解决的 promise 会造成内存泄漏或性能下降吗?

would sharing a global resolved promise between many promise chains create a memory leak or have performance downsides?

我经常陷入这样的模式:

function makeSureDataWasFetched () {
  if (dataAlreadyFetched) {
    // Creating a new empty resolved promise object and returning it
    // to keep the function interface consistent
    return Promise.resolve()
  } else {
    return fetchData()
  }
}

因此,为了避免每次都重新创建一个新的空已解决的承诺对象,我一直在考虑共享一个唯一的、全局已解决的承诺

const resolved = Promise.resolve()

// then, re-use this resolved promise object all over the place
function makeSureDataWasFetched () {
  if (dataAlreadyFetched) {
    // Return the shared resolved promise object,
    // sparing the creation of a new resolved promise object
    return resolved
  } else {
    return fetchData()
  }
}

function makeSureSomethingElseWasFetched () {
  if (thatSomethingElseWasAlreadyFetched) {
    return resolved
  } else {
    return fetchSomethingElse()
  }
}

// etc

在整个应用程序中被引用,这个已解决的承诺将永远不会被垃圾收集。 因此,如果它保留一些对使用它的承诺链的引用,那么这些也不会被垃圾收集并且会造成内存泄漏,对吗?

所以我的问题是:这种全局已解决的承诺是否会保留对 Bluebird 实现中所有依赖承诺链的引用?在香草 ES6 Promises 中? 如果不是,那会不会有任何性能下降来抵消每次创建新的已解决承诺的节省成本?

Would sharing a global resolved promise between many promise chains create a memory leak or have performance downsides?

没有

重用和共享全局解析的承诺只会使单个全局解析的承诺永远不会被垃圾收集。它不会影响可能链接到它的其他承诺的垃圾收集。当它们不再像通常发生的那样可访问时,它们将被垃圾收集。

现在,尚不清楚共享全球解决的承诺有什么好处。它不应该被需要。任何时候你想要一个已经解决的承诺,你可以用 Promise.resolve() 创建一个,然后你的代码不依赖于共享的全局并且可以更加模块化。

So my question: would this kind of global resolved promise keep a reference to all those depending promise chains in Bluebird implementation?

没有

In vanilla ES6 Promises?

没有

If not, would that have any performance downside counter-balancing the spared cost of creating new resolved promises every time?

您问的是过早的微优化,这几乎不是一个好主意。如果在未来的某个时候,您想完全优化您的代码的性能,那么您将进行概要分析和测量,我可以向您保证,您会发现 100 件您可以处理的事情,这些事情对您的代码的影响远远超过尝试共享全局以实现已解决的承诺。


为了帮助您了解谁引用了 promise 链接中的内容以及什么时候可以进行垃圾回收,让我描述一下链接的工作原理。假设您有以下代码:

f1().then(f2).then(f3)

f1f2 都是 return 承诺,我们称之为 P1P2.

所以,这是进度:

  1. 调用 f1(),它 return 是一个承诺 P1
  2. 调用 P1.then(f2) return 我们将调用 P3 的新承诺。
  3. 调用 P3.then(f3) return 我们将调用 'P4' 的新承诺。
  4. 然后,在未来的某个时刻,P1 被解析并触发它调用它的 .then() 处理程序。
  5. f2 被调用,它 return 承诺 P2
  6. 当 promise .then() 代码的内部从 f2 .then() 处理程序获取 return 值作为 return 值时,它检测到这个 return 值是一个承诺,然后它将新的承诺 P2 链接到 P3。它通过将自己的 .then() 处理程序添加到 P2 来做到这一点,以便它可以跟踪其状态。在 P3 内部,它将这个新的 .then() 处理程序添加到 P3 可以解决之前必须发生的事情列表中。请注意,P2P3 之间没有直接引用。他们甚至没有相互直接引用。相反,P3 正在等待调用特定的 .then() 处理程序(恰好附加到 P2)。
  7. 然后,在将来某个时间 P2 解决。
  8. 这将调用在第 6 步中建立的内部 .then() 处理程序,并清除对该 .then() 处理程序的任何引用。这告诉 P3 它现在可以自行解析,这会取消它对 P2 的间接引用的链接。此时,此 promise 链中的任何内容都不再对 P2 有任何引用,因此如果您的代码中其他地方没有对它的其他引用,则可以对其进行 GC。
  9. P3 解析时,它会调用其 .then() 处理程序,这些处理程序将执行 f3 告诉您的代码承诺链现已完成。

所以,从这里我希望你能看到链式承诺实际上并不存储对彼此的引用。父 promise 以子 promise 上的 .then() 处理程序结束,这使得子 promise 在 .then() 处理程序被调用之前不会被 GC,一旦 .then() 处理程序被调用,即使是间接的两个 promise 之间的联系被切断,每个 promise 都可以独立地用于 GC(只要在其他代码中没有对它们的其他引用)。

根据你的问题,如果 P2 恰好是你共享的、已经解决的全局承诺,那么第 6 步只会向它添加一个 .then() 处理程序,它将在下一个调用打勾(因为底层的承诺已经解决)并且一旦 .then() 处理程序被调用,将不再有与 P2 的连接,因此它是一个持久的全局或没有。