在非异步函数中使用"await"

cro*_*ogs 18 javascript asynchronous async-await

我有一个异步函数,由我的代码中的某个地方的setInterval运行.此函数定期更新某些缓存.

我还有一个不同的同步函数需要检索值 - 最好是从缓存中检索,如果它是缓存未命中,那么从数据源(我意识到以同步方式进行IO操作是不明智的,但我们假设在这种情况下这是必需的).

我的问题是我希望同步函数能够等待来自异步函数的值,但是不可能await在非async函数内使用关键字:

function syncFunc(key) {
    if (!(key in cache)) {
        await updateCacheForKey([key]);
    }
}

async function updateCacheForKey(keys) {
    // updates cache for given keys
    ...
}
Run Code Online (Sandbox Code Playgroud)

现在,通过将内部逻辑提取updateCacheForKey到新的同步函数中,并从现有函数中调用此新函数,可以轻松地避免这种情况.

我的问题是为什么首先绝对防止这个用例?我唯一的猜测是它与"白痴"有关,因为在大多数情况下,从同步函数等待异步函数是错误的.但是我认为它有时会有其有效的用例我错了吗?

(我认为这在C#中也可以使用Task.Wait,虽然我可能会在这里混淆一​​些事情).

T.J*_*der 26

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

他们不能,因为:

  1. JavaScript基于由线程处理的"作业队列"工作,其中作业具有运行到完成语义,以及

  2. JavaScript并没有真正的异步功能(真的 - 坚持我这个......)

作业队列(事件循环)在概念上非常简单:当需要完成某些事情(脚本的初始执行,事件处理程序回调等)时,该工作将被放入作业队列中.为该作业队列提供服务的线程获取下一个待处理作业,将其运行完成,然后返回下一个作业.(当然,它比这更复杂,但这足以满足我们的目的.)因此,当一个函数被调用时,它被调用作为处理作业的一部分,并且作业总是在下一个作业运行之前处理完成.

运行完成意味着如果作业调用了一个函数,那么该函数必须在作业完成之前返回.当线程运行以执行其他操作时,作业不会在中间暂停.这使得代码显然更容易正确编写和推理,而不是在其他事情发生时,作业可能会被暂停.(同样,它比这更复杂,但这又足以满足我们的目的.)

到现在为止还挺好.这有什么关于没有真正拥有异步功能的东西?!

虽然我们谈论"同步"和"异步"函数,甚至有一个async关键字我们可以应用于函数,但函数调用在JavaScript中始终是同步的.异步函数并不存在.我们有同步函数可以设置环境稍后会调用的回调(通过排队作业).

让我们假设updateCacheForKey看起来像这样:

async function updateCacheForKey(key) {
    const value = await fetch(/*...*/);
    cache[key] = value;
    return value;
}
Run Code Online (Sandbox Code Playgroud)

在幕后,真正做的是:

function updateCacheForKey(key) {
    return fetch(/*...*/).then(result => {
        const value = result;
        cache[key] = value;
        return value;
    });
}
Run Code Online (Sandbox Code Playgroud)

它要求浏览器启动获取数据的过程,并向其注册回调(通过then),以便浏览器在数据返回时调用,然后退出,返回承诺then.数据尚未提取,但updateCacheForKey已完成.它已经回来了.它同步完成了它的工作.

稍后,当获取完成时,浏览器将作业排队以调用该promise响应; 当从队列中获取该作业时,将调用该回调,并且其返回值用于解析then返回的promise .

我的问题是为什么首先绝对防止这个用例?

让我们看看它会是什么样子:

  1. 线程获取一个工作,该工作涉及调用syncFunc,调用updateCacheForKey.updateCacheForKey要求浏览器获取资源并返回其承诺.通过这种非异步的魔力await,我们同步等待该承诺得到解决,从而阻止了这项工作.

  2. 在某些时候,浏览器的网络代码完成检索资源并将作业排队以调用我们注册的promise回调updateCacheForKey.

  3. 什么都没发生,再一次.:-)

...因为作业具有运行到完成语义,并且在完成上一个作业之前不允许该线程接收下一个作业.不允许该线程挂起syncFunc在中间调用的作业,以便它可以处理解析promise的作业.

这似乎是随意的,但同样,它的原因在于它使得编写正确的代码和推理代码正在做的事情变得非常容易.

但它确实意味着"同步"功能不能等待"异步"功能完成.

上面有很多细节等等.如果你想深入了解它的细节,你可以深入研究规范.包装很多条款和温暖的衣服,你会有一段时间.:-)


Dar*_*ook 7

这展示了函数如何既可以是同步的,也可以是异步的,以及“立即调用函数表达式”习惯用法如何仅在被调用函数的路径执行同步操作时才是立即的。

function test() {
    console.log('Test before');
    (async () => await print(0.3))();
    console.log('Test between');
    (async () => await print(0.7))();
    console.log('Test after');
}

async function print(v) {
    if(v<0.5)await sleep(5000);
    else console.log('No sleep')
    console.log(`Printing ${v}`);
}

function sleep(ms : number) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

test();
Run Code Online (Sandbox Code Playgroud)

(基于Ayyappa在另一个答案的评论中的代码。)

console.log 如下所示:

16:53:00.804 Test before
16:53:00.804 Test between
16:53:00.804 No sleep
16:53:00.805 Printing 0.7
16:53:00.805 Test after
16:53:05.805 Printing 0.3
Run Code Online (Sandbox Code Playgroud)

如果将 0.7 更改为 0.4,则一切都会异步运行:

17:05:14.185 Test before
17:05:14.186 Test between
17:05:14.186 Test after
17:05:19.186 Printing 0.3
17:05:19.187 Printing 0.4
Run Code Online (Sandbox Code Playgroud)

如果将这两个数字更改为超过 0.5,则一切都会同步运行,并且根本不会创建任何 Promise:

17:06:56.504 Test before
17:06:56.504 No sleep
17:06:56.505 Printing 0.6
17:06:56.505 Test between
17:06:56.505 No sleep
17:06:56.505 Printing 0.7
17:06:56.505 Test after
Run Code Online (Sandbox Code Playgroud)

不过,这确实暗示了原始问题的答案。你可以有这样的函数(免责声明:未经测试的nodeJS代码):

const cache = {}

async getData(key, forceSync){
if(cache.hasOwnProperty(key))return cache[key]  //Runs sync

if(forceSync){  //Runs sync
  const value = fs.readFileSync(`${key}.txt`)
  cache[key] = value
  return value
  }

//If we reach here, the code will run async
const value = await fsPromises.readFile(`${key}.txt`)
cache[key] = value
return value
}
Run Code Online (Sandbox Code Playgroud)


cyc*_*130 6

现在,可以通过将 updateCacheForKey 内部的逻辑提取到一个新的同步函数中,并从两个现有函数中调用这个新函数来轻松规避此问题。

TJ Crowder完美地解释了 JavaScript 中异步函数的语义。但我认为上面的段落值得更多讨论。根据具体updateCacheForKey情况,可能无法将其逻辑提取到同步函数中,因为在 JavaScript 中,有些事情只能异步完成。例如,无法同步执行网络请求并等待其响应。如果updateCacheForKey依赖服务器响应,则无法变成同步函数。

即使在异步函数和 Promise 出现之前也是如此:XMLHttpRequest例如,获取回调并在响应准备好时调用它。无法同步获取响应。Promise 只是回调的抽象层,异步函数只是 Promise 的抽象层。

现在,这本来可以采取不同的方式。在某些环境下这样的:

  • PHP中,几乎一切都是同步的。您使用curl发送请求,并且您的脚本会阻塞,直到它得到响应。
  • Node.js有其文件系统调用的同步版本(readFileSyncwriteFileSync,这些调用会阻塞直到操作完成。
  • 即使是普通的旧浏览器 JavaScript 也有alert和它的朋友 ( confirm, prompt) ,它们会阻塞,直到用户关闭模式对话框。

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

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

这是一个设计决定。

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

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

唯一的选择是在 JavaScript 中允许多线程,众所周知,用 JavaScript 编写正确的程序非常困难。您是否对异步函数感到困惑?尝试信号量!


小智 5

您可以在非异步方法中使用异步函数调用,如下所示

注意:(async()=>等待updateCacheForKey([key]));

function syncFunc(key) {
   if (!(key in cache)) {
      (async () => await updateCacheForKey([key]));
   }
}

async function updateCacheForKey(keys) {
   // updates cache for given keys
   ...
}
Run Code Online (Sandbox Code Playgroud)

  • 这似乎没有等到函数完成!https://codesandbox.io/s/t6cwg?file=/index.ts (17认同)
  • 这只是创建另一个异步函数并将“await”放在那里,然后调用不带“await”的外部异步函数。因此,外部函数将运行直到到达“await”,然后将控制权返回给“syncFunc”。然后,“await”之后的所有内容都将在“syncFunc”完成后运行。您可以通过直接从“runSync”调用“updateCacheForKey”来实现类似的目标。 (8认同)
  • 我投了反对票,因为如果没有更多解释,这会产生误导:如果 `updateCacheForKey()` 实际上执行任何异步操作,则不会立即调用它。在这种情况下,*立即*发生的就是创建一个 Promise(稍后执行)。*但是*如果 updateCacheForKey() 真正同步,没有返回 Promise 或执行等待,那么它将立即调用它。在这种情况下,这是一个非常酷的技巧,可以绕过只有“async”函数可以使用“await”的限制。(顺便说一句,被调用的函数有时可以是同步的,有时是异步的!) (4认同)
  • 我相信您将需要调用匿名异步函数:`(async()=&gt;等待updateCacheForKey([key]))();` (3认同)
  • 这个 hack 在任何时候都解决了 0 个问题。您仍在创建一个悬而未决的承诺,它只是包装在另一个异步函数中。您可以将异步函数放在此函数之外,而不是放在 IIFE 中,然后在不等待的情况下调用它,显然您会遇到与以前相同的问题。使其成为 IIFE 并不能解决任何问题。不知道这怎么得到这么多人的支持,但这是完全错误的 (3认同)
  • 另请参阅[如何在顶层使用 async/await?](/sf/ask/3256103511/)。 (2认同)