JavaScript中的承诺是处理异步操作的一种方式。异步操作中最棘手的部分之一是,我们可能会意外地有记忆泄漏,因此,了解何时诺言以及阻止承诺被收集的承诺非常重要。
tl&drs
-
This codesandbox具有所有诺言的例子,然后是链条。打开沙箱并重新加载。您可以首先运行
Promise example
,然后then example
。 -
在文章的末尾,您会从文章中找到关键发现。
我们将使用 请不要在开发工具控制台中运行实验。开发工具可以使所有对象都活着,因此您将获得错误的阴性结果。 cinalizationRegistry在控制台和时间支出中打印。 重要的是要牢记,如果承诺有资格获得GC,这并不意味着它将立即收集。 JavaScript中的GC过程不是确定性的,并且由JavaScript运行时酌情发生。有关工具的一些注释,我们将使用
本文适用于高级用户,它研究了GC(垃圾收集者)如何与承诺合作。如果您想获得有关承诺的基本信息,请查看该系列中的早期文章。
FinalizationRegistry
查找元素GCED何时。如果您不熟悉FinalizationRegistry
,请检查this article。
为了加快GC的速度,我们将创建大量的模拟对象,并删除它们的强大参考。
让我们起草可能的方案:
明确保留对承诺的参考:
const promise = new Promise((resolve, reject) => {...});
在这种情况下,我们对承诺有很大的提及,只要const promise
存在,我们就不会GC。
ð只要您明确提及自己的诺言,它就不会被
失去对承诺,解决和拒绝职能的参考:
let promiseWithoutResolve = new Promise((resolve) => {
setTimeout(() => {
console.log("Timeout for the promise that keeps no refs");
}, 100000);
});
finalizationRegistry.register(promiseWithoutResolve, " which keeps no references");
promiseWithoutResolve = null;
对于该测试,我们既不保留强大的ref to the promise
,也不保留resolve
函数,但是,Promist功能具有很长的超时操作。
ð当您将所有参考丢失到resolve
,reject
和实例本身时,对象将标记为GC,一旦GC启动,就会收集它。 (注意:JS GC有几代人,因此,如果您的承诺是第三代,则可能不会收集)。
cache resolve
和/或reject
方法没有强大的参考本身
在实际代码库中,您可以找到类似的东西:
let resolve;
let promise = new Promise((_resolve) => {
resolve = _resolve;
});
// Let's remove the reference to promise
promise = null;
此代码执行2件事:
1)删除对承诺的有力参考
2)caches resolve
在承诺构造函数中的回调外函数。
我们不再可以直接访问这一承诺,但是目前尚不清楚该诺言是否会排队为GC排队,或者只要我们将链接保留到resolve
。
要测试这种行为,我们可以起草一个实验:
let promiseWithResolve = new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 100000);
});
finalizationRegistry.register(promiseWithResolve , " which keeps resolve function");
promiseWithResolve = null;
对于这样的实验,我们有一个输出:
Promise which keeps resolve function. Time taken: 155765.
ð只要我们提到resolve
或reject
回调,承诺就会保持在记忆中。
ð如果我们有一个长期生活的诺言,它可能会到达老一辈,这意味着在失去所有裁判并获得诺言之间需要更多的时间。
.then
链接
日常用例:
new Promise(() => {}).then(() => {/* Some async code */})
测试的实验:
let promiseWithThen = new Promise(() => {});
let then = promiseWithThen.then(() => {
console.log("then reached");
});
finalizationRegistry.register(promiseWithThen, " with `then` chain");
finalizationRegistry.register(then, " then callback");
promiseWithThen = then = null;
输出:
Promise with `then` chain. Time taken: 191.
Promise then callback. Time taken: 732.
原始的promiseWithThen
将永远无法解决,但是它是通过遵循then
操作链接的,这可能会保留对原始承诺的参考。幸运的是,then
并不能阻止承诺被淘汰。
ð.then
不能阻止GC的承诺。仅明确提及承诺自己,resolve
和reject
很重要。
如果我们将.then
添加到这些实验中会发生什么?
正如我们发现的,.then
。不能阻止垃圾收集的承诺。这意味着,它不应有任何效果。
为了证明这一点,我们可以在此沙盒中起草Then example
下的实验:https://codesandbox.io/s/promises-article-first-example-8jfyh?file=/src/index.js
运行实验时,您会看到then
确实没有效果,并且不会改变任何行为。
为什么几个。然后连锁继续保证?
有时在代码中,我们有链条:
Promise.resolve()
.then(asyncCode1)
.then(asyncCode2)
...
.then(asyncCodeN);
即使我们不遵守承诺,也没有计划在链条完成之前为GC。
问题是:Promise.resolve()
返回了解决的承诺,并且第一个.then
计划运行一个微型,因为以前的承诺已解决。因此,我们将有一个计划执行的范围。
.then
返回了由以下异步操作(asyncCode2
,...)链接的承诺。异步操作的值是以前的.then
块的返回值(在我们的情况下,是fn asyncCode1
)。
ð如果您的.then
链获得“控制”(计划执行甚至启动执行),则该功能范围具有解决或拒绝.then
返回的承诺的参考,因此该诺言不会是ged。
总结:
关键发现的简短列表:
ð引用:promise instance
本身,resolve
,reject
遵守垃圾收集的承诺。
ð.then
不能阻止GC的承诺。
ð如果您的.then
链获得“控制”(计划执行甚至启动执行),则该功能范围具有解决或拒绝.then
返回的承诺的参考,因此该诺言不会是ged。
ð如果我们有一个长期生活的诺言,它可能会到达老一辈,这意味着在失去所有裁判和诺言之间需要更多的时间。<<<<<<<<< /p>