我有一个异步函数,它由代码中某处的 setInterval 运行。此函数定期更新一些缓存。
我还有一个不同的同步函数需要检索值 - 最好是从缓存中检索,但如果是缓存未命中,则从数据源检索(我意识到以同步方式进行 IO 操作是不明智的,但让我们假设在这种情况下这是必需的)。
我的问题是我希望同步函数能够等待来自异步函数的值,但是不可能在非 async
函数中使用 await
关键字:
function syncFunc(key) {
if (!(key in cache)) {
await updateCacheForKey([key]);
}
}
async function updateCacheForKey(keys) {
// updates cache for given keys
...
}
现在,通过将 updateCacheForKey
中的逻辑提取到一个新的同步函数中,并从两个现有函数中调用这个新函数,就可以轻松规避这种情况。
我的问题是为什么首先要绝对阻止这个用例?我唯一的猜测是它与“防白痴”有关,因为在大多数情况下,从同步函数等待异步函数是错误的。但我认为它有时有其有效用例是不是错了?
(我认为这在 C# 中也可以通过使用 Task.Wait
来实现,尽管我在这里可能会混淆一些东西)。
原文由 crogs 发布,翻译遵循 CC BY-SA 4.0 许可协议
他们不能,因为:
JavaScript 以线程处理的“作业队列”为基础工作,其中作业具有运行到完成的语义,并且
JavaScript 并没有真正的异步函数——甚至
async
函数在幕后是返回承诺的同步函数 (详情如下)作业队列(事件循环)在概念上非常简单:当需要完成某事时(脚本的初始执行、事件处理程序回调等),该工作将放入作业队列。为作业队列提供服务的线程会拾取下一个挂起的作业,运行它直到完成,然后返回下一个作业。 (当然,它比这更复杂,但这对我们的目的来说已经足够了。) 所以当一个函数被调用时,它被称为作业处理的一部分,并且作业总是在下一个作业运行之前处理完成。
运行完成意味着如果作业调用函数,则该函数必须在作业完成之前返回。当线程跑去做其他事情时,作业不会在中间暂停。这使得 代码 更容易正确编写和推理,而不是在发生其他事情时作业可能会在中间暂停。 (再一次,它比这更复杂,但这再次足以满足我们的目的。)
到目前为止,一切都很好。没有真正的异步函数是怎么回事?!
尽管我们谈论“同步”与“异步”函数,甚至有一个
async
关键字我们可以应用于函数,但函数调用在 JavaScript 中始终是 同步 的。async
函数是一个函数,它 同步 返回函数逻辑 稍后 实现或拒绝的承诺,排队环境稍后调用的回调。让我们假设
updateCacheForKey
看起来像这样:在幕后,它 真正 在做的是 (非常粗略地,不是字面意思) :
(我在我最近的书 JavaScript:新玩具 的第 9 章中对此进行了更详细的介绍。)
它要求浏览器开始获取数据的过程,并为其注册一个回调(通过
then
)以便浏览器在数据返回时调用, 然后它退出,从then
返回承诺---
。数据尚未获取,但updateCacheForKey
已完成。它回来了。它同步地完成它的工作。_稍后_,当获取完成时,浏览器将作业排队以调用该承诺回调;当从队列中取出该作业时,将调用回调,其返回值用于解析返回的承诺
then
。让我们看看会是什么样子:
线程选择一个作业,该作业涉及调用
syncFunc
,调用updateCacheForKey
。updateCacheForKey
要求浏览器获取资源并返回其承诺。通过这个非异步await
的魔力,我们同步地等待那个承诺被解决,从而阻止了工作。在某个时候,浏览器的网络代码完成检索资源并排队作业以调用我们在
updateCacheForKey
中注册的承诺回调。什么都没有发生,永远不会再发生。 :-)
…因为作业具有运行到完成的语义,并且线程在完成前一个作业之前不允许进行下一个作业。不允许线程在中间挂起调用
syncFunc
的作业,以便它可以继续处理将解决承诺的作业。这似乎是武断的,但同样,这样做的原因是它使编写正确的代码和推断代码的作用变得更加容易。
但这确实意味着“同步”函数不能等待“异步”函数完成。
上面有 很多 细节等等。如果您想深入了解它的本质,可以深入研究规范。带上很多食物和暖和的衣服,你会有一些时间。 :-)