Javascript 在非异步函数中使用“await”
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/47227550/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Using "await" inside non-async function
提问by crogs
I have an async function that runs by a setInterval somewhere in my code. This function updates some cache in regular intervals.
我有一个异步函数,它由我代码中某处的 setInterval 运行。此函数定期更新一些缓存。
I also have a different, synchronous function which needs to retrieve values - preferably from the cache, yet if it's a cache-miss, then from the data origins (I realize making IO operations in a synchronous manner is ill-advised, but lets assume this is required in this case).
我还有一个不同的同步函数,它需要检索值——最好从缓存中检索,但如果是缓存未命中,则从数据源中检索(我意识到以同步方式进行 IO 操作是不明智的,但让我们假设在这种情况下这是必需的)。
My problem is I'd like the synchronous function to be able to wait for a value from the async one, but it's not possible to use the awaitkeyword inside a non-asyncfunction:
我的问题是我希望同步函数能够等待来自异步函数的值,但不可能await在非async函数中使用关键字:
function syncFunc(key) {
if (!(key in cache)) {
await updateCacheForKey([key]);
}
}
async function updateCacheForKey(keys) {
// updates cache for given keys
...
}
Now, this can be easily circumvented by extracting the logic inside updateCacheForKeyinto a new synchronous function, and calling this new function from both existing functions.
现在,通过将内部逻辑提取updateCacheForKey到一个新的同步函数中,并从两个现有函数中调用这个新函数,可以很容易地避免这种情况。
My question is why absolutely prevent this use case in the first place? My only guess is that it has to do with "idiot-proofing", since in most cases, waiting on an async function from a synchronous one is wrong. But am I wrong to think it has its valid use cases at times?
我的问题是为什么首先要绝对阻止这种用例?我唯一的猜测是它与“防白痴”有关,因为在大多数情况下,等待来自同步函数的异步函数是错误的。但是我认为它有时具有有效的用例是错误的吗?
(I think this is possible in C# as well by using Task.Wait, though I might be confusing things here).
(我认为这在 C# 中也可以通过使用Task.Wait,尽管我可能会在这里混淆一些东西)。
回答by T.J. Crowder
My problem is I'd like the synchronous function to be able to wait for a value from the async one...
我的问题是我希望同步函数能够等待来自异步函数的值...
They can't, because:
他们不能,因为:
JavaScript works on the basis of a "job queue" processed by a thread, where jobs have run-to-completion semantics, and
JavaScript doesn't really have asynchronous functions (really — stick with me on this...)
JavaScript 基于线程处理的“作业队列”工作,其中作业具有运行到完成语义,并且
JavaScript 并没有真正的异步函数(真的 - 坚持我这个......)
The job queue (event loop) is conceptually quite simple: When something needs to be done (the initial execution of a script, an event handler callback, etc.), that work is put in the job queue. The thread servicing that job queue picks up the next pending job, runs it to completion, and then goes back for the next one. (It's more complicated than that, of course, but that's sufficient for our purposes.)So when a function gets called, it's called as part of the processing of a job, and jobs are always processed to completion before the next job can run.
作业队列(事件循环)在概念上非常简单:当需要完成某件事时(脚本的初始执行、事件处理程序回调等),该工作被放入作业队列中。为该作业队列提供服务的线程接收下一个挂起的作业,运行它直到完成,然后返回进行下一个作业。(当然,它比那更复杂,但这对于我们的目的来说已经足够了。)因此,当一个函数被调用时,它作为作业处理的一部分被调用,并且在下一个作业可以运行之前,作业总是被处理完成。
Running to completion means that if the job called a function, that function has to return before the job is done. Jobs don't get suspended in the middle while the thread runs off to do something else. This makes code dramaticallysimpler to write correctly and reason about than if jobs could get suspended in the middle while something else happens. (Again it's more complicated than that, but again that's sufficient for our purposes here.)
运行到完成意味着如果作业调用了一个函数,则该函数必须在作业完成之前返回。当线程跑去做其他事情时,作业不会在中间暂停。这使得代码大幅简单正确书写和推理相比,就业岗位可能会被停在中间,而别的东西发生。(同样,它比那更复杂,但这对于我们这里的目的来说已经足够了。)
So far so good. What's this about not really having asynchronous functions?!
到现在为止还挺好。没有真正的异步函数是什么意思?!
Although we talk about "synchronous" vs. "asynchronous" functions, and even have an asynckeyword we can apply to functions, a function call is always synchronousin JavaScript. Asynchronous functions don't really exist. We have synchronous functions that can set up callbacks the environment will call later (by queuing a job) when appropriate.
尽管我们谈论“同步”与“异步”函数,甚至有一个async关键字可以应用于函数,但在 JavaScript 中函数调用始终是同步的。异步函数实际上并不存在。我们有同步函数,可以设置环境稍后(通过排队作业)在适当的时候调用的回调。
Let's assume updateCacheForKeylooks something like this:
让我们假设updateCacheForKey看起来像这样:
async function updateCacheForKey(key) {
const value = await fetch(/*...*/);
cache[key] = value;
return value;
}
What that's reallydoing, under the covers, is this:
在幕后,它真正在做的是:
function updateCacheForKey(key) {
return fetch(/*...*/).then(result => {
const value = result;
cache[key] = value;
return value;
});
}
It asks the browser to start the process of fetching the data, and registers a callback with it (via then) for the browser to call when the data comes back, and then it exits, returning the promise from then. The data isn't fetched yet, but updateCacheForKeyis done. It has returned. It did its work synchronously.
它要求浏览器开始获取数据的过程,并向它注册一个回调(via then),以便浏览器在数据回来时调用,然后退出,从 返回承诺then。尚未获取数据,但updateCacheForKey已完成。它已经回来了。它同步完成了它的工作。
Later, when the fetch completes, the browser queues a job to call that promise callback; when that job is picked up from the queue, the callback gets called, and its return value is used to resolve the promise thenreturned.
稍后,当获取完成时,浏览器将作业排队以调用该承诺回调;当从队列中提取该作业时,将调用回调,其返回值用于解析then返回的承诺。
My question is why absolutely prevent this use case in the first place?
我的问题是为什么首先要绝对阻止这种用例?
Let's see what that would look like:
让我们看看它会是什么样子:
The thread picks up a job and that job involves calling
syncFunc, which callsupdateCacheForKey.updateCacheForKeyasks the browser to fetch the resource and returns its promise. Through the magic of this non-asyncawait, we synchronously wait for that promise to be resolved, holding up the job.At some point, the browser's network code finishes retrieving the resource and queues a job to call the promise callback we registered in
updateCacheForKey.Nothing happens, ever again. :-)
该线程接受一项工作,该工作涉及调用
syncFunc,调用updateCacheForKey.updateCacheForKey要求浏览器获取资源并返回其承诺。通过这种非 async 的魔力await,我们同步等待该承诺得到解决,从而阻止了工作。在某个时刻,浏览器的网络代码完成了对资源的检索,并将作业排队以调用我们在 中注册的承诺回调
updateCacheForKey。什么都没有发生,再一次。:-)
...because jobs have run-to-completion semantics, and the thread isn't allowed to pick up the next job until it completes the previous one. The thread isn't allowed to suspend the job that called syncFuncin the middle so it can go process the job that would resolve the promise.
...因为作业具有 run-to-completion 语义,并且线程在完成前一个作业之前不允许接受下一个作业。线程不允许挂起syncFunc在中间调用的作业,因此它可以处理将解决承诺的作业。
That seems arbitrary, but again, the reason for it is that it makes it dramatically easier to write correct code and reason about what the code is doing.
这似乎是随意的,但同样,这样做的原因是它使编写正确的代码和推断代码正在做什么变得更加容易。
But it does mean that a "synchronous" function can't wait for an "asynchronous" function to complete.
但这确实意味着“同步”函数不能等待“异步”函数完成。
There's a lotof hand-waving of details and such above. If you want to get into the nitty-gritty of it, you can delve into the spec. Pack lots of provisions and warm clothes, you'll be some time. :-)
上面有很多细节等等。如果你想深入了解它的本质,你可以深入研究规范。带上很多食物和保暖的衣服,你会得到一些时间。:-)
回答by SpikeEdge
You can call an async function from within a non-async function via an Immediately Invoked Function Expression (IIFE):
您可以通过立即调用函数表达式 (IIFE)从非异步函数中调用异步函数:
(async () => await updateCacheForKey([key]))();
And as applied to your example:
并适用于您的示例:
function syncFunc(key) {
if (!(key in cache)) {
(async () => await updateCacheForKey([key]))();
}
}
async function updateCacheForKey(keys) {
// updates cache for given keys
...
}
回答by cyco130
Now, this can be easily circumvented by extracting the logic inside updateCacheForKey into a new synchronous function, and calling this new function from both existing functions.
现在,可以通过将 updateCacheForKey 中的逻辑提取到一个新的同步函数中,并从两个现有函数中调用这个新函数来轻松规避这一点。
T.J. Crowderexplains the semantics of async functions in JavaScript perfectly. But in my opinion the paragraph above deserves more discussion. Depending on what updateCacheForKeydoes, it may not be possible to extract its logic into a synchronous function because, in JavaScript, some things can only be done asynchronously. For example there is no way to perform a network request and wait for its response synchronously. If updateCacheForKeyrelies on a server response, it can't be turned into a synchronous function.
TJ Crowder完美地解释了 JavaScript 中异步函数的语义。但在我看来,上面的段落值得更多讨论。根据updateCacheForKey它的作用,可能无法将其逻辑提取到同步函数中,因为在 JavaScript 中,某些事情只能异步完成。例如,无法同步执行网络请求并等待其响应。如果updateCacheForKey依赖于服务器响应,则无法将其转换为同步功能。
It was true even before the advent of asynchronous functions and promises: XMLHttpRequest, for instance, gets a callback and calls it when the response is ready. There's no way of obtaining a response synchronously. Promises are just an abstraction layer on callbacks and asynchronous functions are just an abstraction layer on promises.
即使在异步函数和承诺出现之前也是如此:XMLHttpRequest例如,获取回调并在响应准备好时调用它。无法同步获得响应。Promise 只是回调的抽象层,异步函数只是 Promise 的抽象层。
Now this could have beendone differently. And it isin some environments:
现在,这可以以不同的方式完成。它是在一定的环境中:
- In PHP, pretty much everything is synchronous. You send a request with curl and your script blocks until it gets a response.
- Node.jshas synchronous versions of its file system calls (
readFileSync,writeFileSyncetc.) which block until the operation completes. - Even plain old browser JavaScript has
alertand friends (confirm,prompt) which block until the user dismisses the modal dialog.
- 在PHP 中,几乎一切都是同步的。你用 curl 发送一个请求,你的脚本会阻塞直到它得到响应。
- Node.js的具有其文件系统调用(同步版本
readFileSync,writeFileSync等等),其块直到操作完成。 - 即使是普通的旧浏览器 JavaScript 也有
alert和朋友 (confirm,prompt) 阻止直到用户关闭模式对话框。
This demonstrates that the designers of the JavaScript language could haveopted for synchronous versions of XMLHttpRequest, fetchetc. Why didn't they?
这表明,JavaScript语言的设计者可能已经选择了的同步版本XMLHttpRequest,fetch等他们为什么不?
[W]hy absolutely prevent this use case in the first place?
[W]为什么首先要绝对阻止这个用例?
This is a design decision.
这是一个设计决定。
alert, for instance, prevents the user from interacting with the rest of the page because JavaScript is single threaded and the one and only thread of execution is blocked until the alertcall completes. Therefore there's no way to execute event handlers, which means no way to become interactive. If there was a syncFetchfunction, it would block the user from doing anything until the network request completes, which can potentially take minutes, even hours or days.
alert例如,阻止用户与页面的其余部分进行交互,因为 JavaScript 是单线程的,并且唯一的执行线程会被阻塞,直到alert调用完成。因此无法执行事件处理程序,这意味着无法进行交互。如果有一个syncFetch功能,它会阻止用户做任何事情,直到网络请求完成,这可能需要几分钟,甚至几小时或几天。
This is clearly against the nature of the interactive environment we call the "web". alertwas a mistake in retrospect and it should not be used except under very few circumstances.
这显然违背了我们称之为“网络”的交互环境的性质。alert回想起来是一个错误,除非在极少数情况下,否则不应使用它。
The only alternative would be to allow multithreading in JavaScript which is notoriously difficult to write correct programs with. Are you having trouble wrapping your head around asynchronous functions? Try semaphores!
唯一的选择是在 JavaScript 中允许多线程,这是众所周知的难以编写正确程序的方法。您是否对异步函数难以理解?试试信号量!

