当一个 Promise 永远不会 resolve 时会发生什么?

32

我有一段非常令人困惑的使用es6 async await语法的代码片段。我本来期望的情况是,进程会在await行上永远挂起,因为没有调用resolve函数。然而,实际情况是输出了“start”,然后进程退出并没有更多输出。

const simple = async () => {
  console.log('start')
  await new Promise(resolve => {})
  console.log('done.')
}
simple()

然而,下面的代码将打印“start”,等待1秒钟,然后打印“done”。

const simple = async () => {
  console.log('start')
  await new Promise(resolve => setTimeout(resolve, 1000))
  console.log('done.')
}
simple()

我对这个意思的最接近猜测(没有任何证据)是当节点等待承诺时,它会跟踪在您的代码中正在发生的活动事物,当没有其他事情发生时,它就会退出。有人能解释一下为什么代码在这里退出吗?

运行node v8.7.0


还有一件事情值得一提,以便更容易理解这里发生了什么。由于它是一个async/await函数,它与常规函数不同,后者总是运行到完成。如果您重写simple以不使用async/await,则在await语句之后执行的代码将必须放置在.then()回调内。由于您没有调用resolve(),因此该代码将永远不会执行,因此控制台不会打印“done”。Promise的主体立即执行,由于它为空,因此程序没有剩余可执行的内容! - Avius
1
一个很好的观点!如果没有async/await语法,思考这个问题时为什么代码停止执行就会更加明显。 - andykais
一个好的解释可以在这里找到: https://dev59.com/fVoV5IYBdhLWcg3wErWD#36735167 - marciowb
2个回答

13
我的最佳猜测是…它跟踪您的代码中正在发生的活动,当没有其他事情发生时,它会简单地退出。
这基本上是正确的。Node保持类似计时器和网络请求等内容的引用计数。当您进行网络或其他异步请求、设置计时器等时,Node会增加这个引用计数。当时间/请求解决时,Node会从计数中减去。
这个计数就是Node决定在事件循环结束时是否退出的依据。当你到达事件循环的末尾,Node查看这个计数,如果为零,则退出。只是制作一个promise,并不会增加引用计数,因为它不是一个异步请求。
有一次[Bert Belder](Node核心开发者)的演讲对此很有帮助:https://www.youtube.com/watch?v=PNa9OMajw9w

谢谢,那次交流非常有启发性!关于 ref++ref-- 跟踪每个事物以及 Promises 不添加计数的观点,清晰地说明了为什么该程序会退出。 - andykais
2
为什么第三行的 done 没有让进程继续执行呢?还有更多的 JavaScript 代码需要执行啊!我知道空 Promise 不会将回调添加到队列中,但是为什么剩下的脚本被忽略了呢? - pinhead
2
异步函数会将 await 后面的代码转换为另一个函数,只有在给定的 Promise 解决后才会运行。如果 Promise 永远不会解决,那么等待永远不会运行的代码有什么意义呢? - PoolloverNathan
有没有办法在事件循环中创建自己的引用? - CMCDragonkai

11

没错,你对发生的事情有误解。

就像你所说的,由于await调用的原因,代码将永远不会在那之后继续执行,但这并不重要。

当你调用 simple() 时,该方法本身会返回一个promise——而你并没有等待该promise。在调用 simple() 后,你的执行会继续并立即到达程序末尾。

为了更详细地说明,当没有更多回调需要处理时,nodejs将退出。当你返回你的错误承诺时,你没有在nodejs队列中创建一个回调(例如,如果你进行http请求,你会创建一个回调)。如果你做些什么来保持nodejs活动状态,你会发现 done 永远不会被执行。

const simple = async () => {
  console.log('start')
  await new Promise(resolve => {})
  console.log('done.')
}
var promise = simple();

// keep the application alive forever to see what happens
function wait () {
    setTimeout(wait, 1000);
};
wait();
如果有待处理的回调(由setTimeout创建,或从加载资源中,或等待http响应),那么nodejs将不会退出。但在你的第一个例子中,你没有创建任何回调 - 你只返回了一个破碎的promise。Nodejs在那里看不到任何"未决的"事情,所以它退出。
基本上,在你的simple调用的最后,你没有更多的代码要执行,也没有任何待处理的事情(没有等待回调),所以nodejs安全地知道没有其他事情需要做。

你是正确的,done 永远不会被执行,但是此代码可以等待一个 Promise 在其中执行某些操作。如果您将 await 行替换为 await new Promise(resolve => setTimeout(resolve, 1000)),则代码将在之后输出“done”,而 simple() 不需要任何处理程序。我已更新我的问题以展示这一点。 - andykais
@andykais 是的!完全正确。这正是我所说的。setTimeout方法创建了一个回调函数,Node.js正在寻找它。如果有任何未决的回调函数,Node.js将不会退出。你的第一个示例没有创建任何未决的回调函数,它只是返回一个Promise。 - caesay

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接