JavaScript函数内Promise存活超生命周期:是run-to-completion语义吗?
问题与解答
示例代码
async function sleep(x) { return new Promise((resolve) => { setTimeout(resolve, x * 1000); }); } class Run { async go(x) { console.log('sleep', x); await sleep(x); console.log('wait up', x); } } function runner() { const r = new Run(); // 注意此处没有使用`await` r.go(1); r.go(2); r.go(3); // 注意我们并未从该函数返回`r` } async function main() { runner(); } Promise.resolve().then(() => { return main(); });
脚本输出
sleep 1 sleep 2 sleep 3 wait up 1 wait up 2 wait up 3
问题1:是否形成闭包?
runner()函数未嵌套在其他函数中,无返回值也未被返回给其他调用者,我们是否可认为此处未形成闭包?
解答
是的,这里没有形成闭包。
闭包的核心是函数能够访问自身词法作用域之外的变量,或者内部函数持有对外部作用域的引用,导致外部作用域无法被垃圾回收。而在这个场景中:
runner()没有返回任何内部的函数或引用类型值,外部代码无法持有对局部变量r的引用Run.prototype.go方法的词法作用域不依赖runner()的局部作用域,方法内的x是自身参数,和runner的变量无关runner()执行完毕后,没有外部引用指向r,仅异步任务相关的引用由引擎持有,这和闭包的形成条件无关
问题2:Promise生命周期超出函数的原因
runner()函数内创建的Promise生命周期似乎超出函数本身,通常函数返回后局部变量会被销毁,但Promise会存活至解决。请问这是“run-to-completion”语义导致的,还是其他原因?
解答
这不是run-to-completion语义导致的,核心原因在于JavaScript引擎的异步任务管理机制:
- 调用
r.go(x)时,这个async函数会立即返回一个Promise对象,该Promise会被引擎的微任务队列持有引用 - 内部的
setTimeout属于浏览器/Node.js的定时器模块,定时器触发时,会将resolve回调放入任务队列等待执行 - 只要Promise存在未完成的回调逻辑,或者被引擎的任务队列持有引用,它就不会被垃圾回收,直到所有相关异步逻辑执行完毕
run-to-completion语义仅保证单个同步任务会完整执行,不会被其他任务打断,它只影响同步代码的执行顺序,和异步任务的生命周期没有直接关系。
内容的提问来源于stack exchange,提问作者treecoder
相关产品推荐
相关产品推荐

