使用“等待"内部非异步函数 [英] Using "await" inside non-async function
问题描述
我有一个异步函数,它由我代码中某处的 setInterval 运行.此函数定期更新一些缓存.
I have an async function that runs by a setInterval somewhere in my code. This function updates some cache in regular intervals.
我还有一个不同的同步函数,它需要检索值——最好从缓存中检索,但如果是缓存未命中,则从数据源中检索(我意识到以同步方式进行 IO 操作是不明智的,但让我们假设在这种情况下这是必需的).
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).
我的问题是我希望同步函数能够等待来自异步函数的值,但不可能在非 async 中使用
功能:await
关键字
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 await
keyword inside a non-async
function:
function syncFunc(key) {
if (!(key in cache)) {
await updateCacheForKey([key]);
}
}
async function updateCacheForKey(keys) {
// updates cache for given keys
...
}
现在,可以通过将 updateCacheForKey
中的逻辑提取到一个新的同步函数中,并从两个现有函数中调用这个新函数来轻松规避这一点.
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.
我的问题是,为什么首先要绝对阻止这种用例?我唯一的猜测是它与防白痴"有关,因为在大多数情况下,等待来自同步函数的异步函数是错误的.但我认为它有时具有有效的用例是错误的吗?
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?
(我认为这在 C# 中也可以通过使用 Task.Wait
实现,尽管我可能会在这里混淆).
(I think this is possible in C# as well by using Task.Wait
, though I might be confusing things here).
推荐答案
我的问题是我希望同步函数能够等待来自异步函数的值...
My problem is I'd like the synchronous function to be able to wait for a value from the async one...
他们不能,因为:
JavaScript 基于作业队列"工作由线程处理,其中作业具有运行到完成语义,并且
JavaScript works on the basis of a "job queue" processed by a thread, where jobs have run-to-completion semantics, and
JavaScript 并没有真正的异步函数——即使 async
函数在幕后也是返回 promise 的同步函数(详情如下)
JavaScript doesn't really have asynchronous functions — even async
functions are, under the covers, synchronous functions that return promises (details below)
作业队列(事件循环)在概念上非常简单:当需要完成某件事时(脚本的初始执行、事件处理程序回调等),该工作被放入作业队列中.为该作业队列提供服务的线程接收下一个挂起的作业,运行它直到完成,然后返回进行下一个作业.(当然,它比那更复杂,但这对我们的目的来说已经足够了.) 所以当一个函数被调用时,它被作为工作处理的一部分被调用,并且工作总是被处理到完成在下一个作业可以运行之前.
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 dramatically simpler 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?!
虽然我们谈论的是同步"与异步"函数,甚至有一个 async
关键字我们可以应用于函数,在 JavaScript 中函数调用总是同步.async
函数是一种函数,它同步返回一个承诺,即函数的逻辑实现或拒绝稍后,对环境稍后调用的回调进行排队.
Although we talk about "synchronous" vs. "asynchronous" functions, and even have an async
keyword we can apply to functions, a function call is always synchronous in JavaScript. An async
function is a function that synchronously returns a promise that the function's logic fulfills or rejects later, queuing callbacks the environment will call later.
让我们假设 updateCacheForKey
看起来像这样:
Let's assume updateCacheForKey
looks something like this:
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;
});
}
(我在我最近的书 JavaScript: The New Toys 的第 9 章中详细介绍了这一点>.)
(I go into more detail on this in Chapter 9 of my recent book, JavaScript: The New Toys.)
它要求浏览器开始获取数据的过程,并在其中注册一个回调(通过then
),以便浏览器在数据返回时调用,然后它退出,从then
返回promise.尚未获取数据,但 updateCacheForKey
已完成.它已经回来了.它同步完成其工作.
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 updateCacheForKey
is done. It has returned. It did its work synchronously.
稍后,当获取完成时,浏览器将作业排队以调用该承诺回调;当从队列中取出该作业时,将调用回调,其返回值用于解析返回的承诺 then
.
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 then
returned.
我的问题是为什么首先要绝对阻止这种用例?
My question is why absolutely prevent this use case in the first place?
让我们看看它会是什么样子:
Let's see what that would look like:
线程选择一个作业,该作业涉及调用
syncFunc
,后者调用updateCacheForKey
.updateCacheForKey
要求浏览器获取资源并返回其承诺.通过这种非异步await
的魔力,我们同步等待该承诺得到解决,从而阻止了工作.
The thread picks up a job and that job involves calling
syncFunc
, which callsupdateCacheForKey
.updateCacheForKey
asks 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.
在某个时刻,浏览器的网络代码完成了对资源的检索,并将作业排队以调用我们在 updateCacheForKey
中注册的承诺回调.
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. :-)
...因为作业具有 run-to-completion 语义,并且线程在完成前一个作业之前不允许选择下一个作业.不允许线程在中间挂起调用 syncFunc
的作业,以便它可以处理解决承诺的作业.
...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 syncFunc
in the middle so it can go process the job that would resolve the promise.
这似乎是武断的,但同样,这样做的原因是它使编写正确的代码和推断代码正在做什么变得更加容易.
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 lot of 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. :-)
这篇关于使用“等待"内部非异步函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!