我有一个异步函数,它在代码中的某个地方按setInterval运行。此函数定期更新一些缓存。
我还有一个不同的同步函数,它需要检索值——最好是从缓存中检索,但如果是缓存未命中,则从数据源检索(我意识到以同步方式进行IO操作是不明智的,但假设在这种情况下需要这样做)。
我的问题是,我希望同步函数能够等待异步函数中的值,但是不可能在非异步函数中使用wait
关键字:
function syncFunc(key) {
if (!(key in cache)) {
await updateCacheForKey([key]);
}
}
async function updateCacheForKey(keys) {
// updates cache for given keys
...
}
现在,通过将updateCacheForKey
中的逻辑提取到一个新的同步函数中,并从两个现有函数中调用这个新函数,可以很容易地绕过这个问题。
我的问题是为什么一开始就绝对阻止这个用例?我唯一的猜测是它与“傻瓜证明”有关,因为在大多数情况下,等待同步函数的异步函数是错误的。但我认为它有时有其有效的用例,这是错误的吗?
(我认为在C#中也可以通过使用Task.Wait
,尽管我可能会把这里的事情弄糊涂)。
现在,通过将updateCacheForKey中的逻辑提取到一个新的同步函数中,并从两个现有函数中调用这个新函数,可以很容易地规避这个问题。
T.J.Crowder完美地解释了JavaScript中异步函数的语义。但在我看来,上述段落值得更多讨论。根据updateCacheForKey
的功能,可能无法将其逻辑提取到同步函数中,因为在JavaScript中,某些事情只能异步完成。例如,无法执行网络请求并同步等待其响应。如果updateCacheForKey
依赖于服务器响应,则无法将其转换为同步功能。
甚至在异步函数和promise出现之前也是如此:XMLHttpRequest
,例如,获取回调并在响应就绪时调用它。无法同步获得响应。promise只是回调上的抽象层,异步函数只是promise上的抽象层。
现在,这本可以做得不同。而且在某些环境中:
这表明JavaScript语言的设计者可以选择同步版本的XMLHttpRequest
,fetch
等。为什么他们不选择呢?
[W] 为什么首先要绝对防止这个用例?
这是一个设计决定。
例如,alert
会阻止用户与页面的其余部分交互,因为JavaScript是单线程的,并且只有一个执行线程会被阻止,直到alert
调用完成。因此,无法执行事件处理程序,这意味着无法实现交互。如果有syncFetch
功能,它将阻止用户执行任何操作,直到网络请求完成,这可能需要几分钟,甚至几小时或几天。
这显然违背了我们称之为“网络”的交互环境的本质alert
在retrospect中是一个错误,除非在极少数情况下,否则不应使用它。
唯一的替代方案是允许JavaScript中的多线程,众所周知,JavaScript很难编写正确的程序。您在理解异步函数时有困难吗?试试信号灯!
您可以通过立即调用函数表达式(IIFE)从非异步函数中调用异步函数:
(async () => await updateCacheForKey([key]))();
如你的例子所示:
function syncFunc(key) {
if (!(key in cache)) {
(async () => await updateCacheForKey([key]))();
}
}
async function updateCacheForKey(keys) {
// updates cache for given keys
...
}
我的问题是我希望同步函数能够等待来自异步函数的值。。。
他们不能,因为:
>
JavaScript基于线程处理的作业队列工作,其中作业具有从运行到完成的语义学
JavaScript实际上没有异步函数
作业队列(事件循环)在概念上非常简单:当需要执行某些操作(脚本的初始执行、事件处理程序回调等)时,该工作被放入作业队列中。为该作业队列提供服务的线程将拾取下一个挂起的作业,将其运行到完成,然后返回到下一个作业。(当然,这比这更复杂,但就我们的目的而言,这已经足够了。)因此,当调用函数时,它会作为作业处理的一部分被调用,作业总是在下一个作业运行之前被处理到完成。
运行到完成意味着如果作业调用了函数,则该函数必须在作业完成之前返回。当线程运行以做其他事情时,作业不会被挂在中间。这使得代码写得更简单、更合理,而不是在其他事情发生的情况下,作业可以挂在中间。(同样,它比这更复杂,但对于我们这里的目的来说,这已经足够了。)
到现在为止,一直都还不错。没有异步函数是怎么回事?!
虽然我们讨论了同步与异步函数,甚至有一个我们可以应用于函数的async
关键字,但在JavaScript中,函数调用总是同步的。一个async
函数是一个函数,它同步返回一个promise,该promise函数的逻辑稍后会实现或拒绝,排队等待环境稍后调用的回调。
让我们假设updateCacheForKey
看起来像这样:
async function updateCacheForKey(key) {
const value = await fetch(/*...*/);
cache[key] = value;
return value;
}
在被窝里,真正在做的是(非常粗略,不是字面意思):
function updateCacheForKey(key) {
return fetch(/*...*/).then(result => {
const value = result;
cache[key] = value;
return value;
});
}
(我将在本章中对此进行更详细的介绍
它要求浏览器开始提取数据的过程,并向其注册回调(通过然后
),以便浏览器在数据返回时调用,然后退出,从然后
返回promise。数据尚未提取,但updateCacheForKey
已完成。它回来了。它的工作是同步进行的。
稍后,当获取完成时,浏览器将作业排队以调用该promise回调;当从队列中提取该作业时,将调用回调,其返回值用于解析promise,然后返回。
我的问题是为什么首先绝对阻止这个用例?
让我们看看会是什么样子:
>
线程拾取一个作业,该作业涉及调用
syncFunc
,调用updateCacheForKey
。updateCacheForKey
要求浏览器获取资源并返回其promise。通过这种非异步wait
的魔力,我们同步等待这个promise得到解决,从而阻碍了工作。
在某个时候,浏览器的网络代码完成了对资源的检索,并将一个作业排队,以调用我们在
updateCacheForKey
中注册的promise回调。
再也不会发生任何事情了。:-)
...因为作业已经运行到完成语义,线程在完成前一个作业之前不允许拾取下一个作业。线程不允许在中间挂起名为“代码> SycFunc < /Cord>”的作业,这样它就可以处理将要解决的任务。
这似乎是武断的,但再次强调,其原因是它使编写正确的代码变得非常容易,并说明代码正在做什么。
但这确实意味着“同步”函数不能等待“异步”函数完成。
上面有很多细节和类似的东西。如果你想深入了解它的本质,你可以深入研究它的规格。带上大量的食物和暖和的衣服,你会有一段时间的。:-)
作业和作业队列
- 执行上下文
- 领域和代理
假设有一个进行各种数据库查询的库: 我想同时执行这两个查询,而不必添加到方法签名或添加装饰器。这些功能不应该完全依赖于异步。 使用中的非异步函数的最佳方法是什么? 我在找这样的东西: 提前感谢您的考虑和回复。
问题内容: 假设有一个可以进行各种数据库查询的库: 我想同时执行这两个查询,而不必添加到方法签名或添加装饰器。这些功能完全不应该依赖于异步。 在其中利用这些非异步功能的最佳方法是什么? 我正在寻找某种形式的东西: 预先感谢您的考虑和回应。 问题答案: 如果某个函数在本质上是阻塞的而不是异步的,则在事件循环内运行该函数的唯一正确方法是使用run_in_executor在线程内运行该函数: 它确实起作
问题内容: 我想要实现的是Web钩子应该等待,直到我从api调用获得响应为止。PS:API正在起作用,只是机器人不等待响应到来。任何帮助将不胜感激。谢谢 问题答案: 您没有说明要在什么环境中运行,但是鉴于您的代码以及上面概述的更改,我可以使用Firebase Cloud Functions和node.js 6.14复制您的问题。 我可以通过使用包而不是来使它正常工作。顾名思义,它使用本机Promi
问题内容: 我试图将两个异步函数链接在一起,因为第一个具有条件返回参数,导致第二个运行或退出模块。但是,我发现规格中找不到奇怪的行为。 这是我的代码的混帐摘要(您可以在此处查看完整的范围),该代码只是检查玩家是否已经在大厅中,但这无关紧要。 接下来,我们有这个异步功能。 如果,则无需运行此功能。 我试着做 我希望这将等待结果,以便可以有条件地运行,但是我收到了没有具体细节的类型错误。 为什么您不能
问题内容: 我想使用生成器yield和async函数。我阅读了本主题,并编写了以下代码: 但是我得到了错误: 语法错误:异步函数中的“ yield” 如何在异步功能中使用良率生成器? 问题答案: 更新: 从Python 3.6开始,我们有了异步生成器,能够直接在协程内部使用。 Python 3.5的旧答案: 您不能在协程内部。唯一的方法是使用/ magic方法手动实现异步迭代器。在您的情况下: 输
问题内容: 如何从异步函数返回值?我试图喜欢这个 它给了我, 问题答案: 您不能超出范围。为了获得预期的结果,您应该将其包装到异步IIFE中,即 样品。 有关更多信息 由于返回一个Promise,因此可以将其省略,如下所示: 然后像以前一样做