当前位置: 首页 > 知识库问答 >
问题:

在非异步函数中使用“wait”

万喜
2023-03-14

我有一个异步函数,它在代码中的某个地方按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,尽管我可能会把这里的事情弄糊涂)。

共有3个答案

通宾白
2023-03-14

现在,通过将updateCacheForKey中的逻辑提取到一个新的同步函数中,并从两个现有函数中调用这个新函数,可以很容易地规避这个问题。

T.J.Crowder完美地解释了JavaScript中异步函数的语义。但在我看来,上述段落值得更多讨论。根据updateCacheForKey的功能,可能无法将其逻辑提取到同步函数中,因为在JavaScript中,某些事情只能异步完成。例如,无法执行网络请求并同步等待其响应。如果updateCacheForKey依赖于服务器响应,则无法将其转换为同步功能。

甚至在异步函数和promise出现之前也是如此:XMLHttpRequest,例如,获取回调并在响应就绪时调用它。无法同步获得响应。promise只是回调上的抽象层,异步函数只是promise上的抽象层。

现在,这本可以做得不同。而且在某些环境中:

  • 在PHP中,几乎所有内容都是同步的。您使用curl发送一个请求,并将脚本阻塞,直到它得到响应

这表明JavaScript语言的设计者可以选择同步版本的XMLHttpRequestfetch等。为什么他们不选择呢?

[W] 为什么首先要绝对防止这个用例?

这是一个设计决定。

例如,alert会阻止用户与页面的其余部分交互,因为JavaScript是单线程的,并且只有一个执行线程会被阻止,直到alert调用完成。因此,无法执行事件处理程序,这意味着无法实现交互。如果有syncFetch功能,它将阻止用户执行任何操作,直到网络请求完成,这可能需要几分钟,甚至几小时或几天。

这显然违背了我们称之为“网络”的交互环境的本质alert在retrospect中是一个错误,除非在极少数情况下,否则不应使用它。

唯一的替代方案是允许JavaScript中的多线程,众所周知,JavaScript很难编写正确的程序。您在理解异步函数时有困难吗?试试信号灯!

阎德业
2023-03-14

您可以通过立即调用函数表达式(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
   ...
}
周辉
2023-03-14

我的问题是我希望同步函数能够等待来自异步函数的值。。。

他们不能,因为:

>

  • 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,调用updateCacheForKeyupdateCacheForKey要求浏览器获取资源并返回其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,因此可以将其省略,如下所示: 然后像以前一样做