Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using "await" inside non-async function

I have an async function that runs by a setInterval somewhere in my code. This function updates some cache in regular intervals.

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).

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     ... } 

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?

(I think this is possible in C# as well by using Task.Wait, though I might be confusing things here).

like image 570
crogs Avatar asked Nov 10 '17 16:11

crogs


People also ask

Can I use await in a non-async function?

You can not use the await keyword in a regular, non-async function. JavaScript engine will throw a syntax error if you try doing so.

What happens if you await a non-async function?

This rule applies when the await operator is used on a non-Promise value. await operator pauses the execution of the current async function until the operand Promise is resolved. When the Promise is resolved, the execution is resumed and the resolved value is used as the result of the await .

Can I await a non-async function Python?

So it is a pretty firm rule that you cannot use await in a non-async function. Whenever asyncio decides it has enough resources it sends that message.

How do you use await inside function?

Inside an async function, you can use the await keyword before a call to a function that returns a promise. This makes the code wait at that point until the promise is settled, at which point the fulfilled value of the promise is treated as a return value, or the rejected value is thrown.


2 Answers

You can call an async function from within a non-async function via an Immediately Invoked Function Expression (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    ... } 
like image 26
SpikeEdge Avatar answered Oct 09 '22 00:10

SpikeEdge


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:

  1. JavaScript works on the basis of a "job queue" processed by a thread, where jobs have run-to-completion semantics, and

  2. 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?!

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.

Let's assume updateCacheForKey looks something like this:

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

What that's really doing, under the covers, is (very roughly, not literally) this:

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

(I go into more detail on this in Chapter 9 of my recent book, JavaScript: The New Toys.)

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.

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:

  1. The thread picks up a job and that job involves calling syncFunc, which calls updateCacheForKey. updateCacheForKey asks the browser to fetch the resource and returns its promise. Through the magic of this non-async await, we synchronously wait for that promise to be resolved, holding up the job.

  2. 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.

  3. Nothing happens, ever again. :-)

...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. :-)

  • Jobs and Job Queues
  • Execution Contexts
  • Realms and Agents
like image 126
T.J. Crowder Avatar answered Oct 09 '22 01:10

T.J. Crowder