Hacker News new | past | comments | ask | show | jobs | submit login
Ask HN: Why can't await be used from non-async functions?
36 points by endorphine on Feb 28, 2022 | hide | past | favorite | 87 comments
I'm trying to wrap my head around async/await and how it works under the hood, be it JavaScript, C# or others.

From what I understand, in JavaScript at least, putting `await foo()` inside an async function, splits the calling function in two, with the 2nd half being converted to a callback. (Pretty sure this is full of errors so please correct me where I'm wrong)

Why can't non-async functions use await()?

I've also read that await() basically preempts the currently running function. How does this work?

Update: I'm re-reading http://journal.stuffwithstuff.com/2015/02/01/what-color-is-y... and I think the answer lies somewhere in this paragraph, but I can't wrap my head around it yet:

> The fundamental problem is “How do you pick up where you left off when an operation completes”? You’ve built up some big callstack and then you call some IO operation. For performance, that operation uses the operating system’s underlying asynchronous API. You cannot wait for it to complete because it won’t. You have to return all the way back to your language’s event loop and give the OS some time to spin before it will be done. Once operation completes, you need to resume what you were doing. The usual way a language “remembers where it is” is the callstack. That tracks all of the functions that are currently being invoked and where the instruction pointer is in each one. But to do async IO, you have to unwind and discard the entire C callstack. Kind of a Catch-22. You can do super fast IO, you just can’t do anything with the result! Every language that has async IO in its core—or in the case of JS, the browser’s event loop—copes with this in some way. [...]

I don't get the "You cannot wait for it to complete because it won’t." or the "But to do async IO, you have to unwind and discard the entire C callstack" parts.

I'm also using these resources, they help but I'm not there yet:

- https://stackoverflow.com/questions/47227550/using-await-ins...

- https://www.youtube.com/watch?v=KmMU5Y_r0Uk




(My academic background is engineering, not computer science, so I'm quite possibly wrong about all of this)

I think what OP is assuming is a multi-threaded or multi-process environment, where the calling function can just block whatever execution context it's running in and wait until the async function returns.

The problem is that many environments are effectively non-multi-threaded, especially the (usually single) thread/queue/process that draws the UI and responds to user input. So if you block the UI thread, your whole app (at least from the standpoint of the user) stops responding.

Still, this should work in principle, but threads are more costly in terms of memory and context switching time than continuations, so it makes sense to allow the thread to continue to handle other tasks while waiting for e.g. I/O to complete.

This is what the author of the classic https://journal.stuffwithstuff.com/2015/02/01/what-color-is-... settles on as the best way of handling async tasks, but I recall there being some pushback on that here on HN.


That's true. I come from the threaded runtimes where we can just block the thread because there's always another thread that can take over. I think that if I try to approach this from a perspective of a single-threaded environment, I might get closer to grasping it.

> The problem is that many environments are effectively non-multi-threaded, especially the (usually single) thread/queue/process that draws the UI and responds to user input

Can you elaborate on why "especially" UI threads/queues/processes are "usually single"?


> Can you elaborate on why "especially" UI threads/queues/processes are "usually single"?

I don't think there's a particularly insightful answer to this question here, it's just that UI frameworks are almost universally written with the assumption that they are only used from one thread. UI frameworks can also use functionality spread across multiple components/libraries/systems, and making an UI kit thread-safe would likely require a lot of effort for dubious benefit (user input normally has to be processed strictly in order because clicking on a button and then pressing "enter" is different from pressing enter and then clicking on a button).

There are some specific scenarios where multiple threads are safe in a UI. For example, it's relatively common to be able to pass off an OpenGL context to another thread... so you can do OpenGL rendering in a thread separate from the main UI thread, if you like. Some UI frameworks specifically support this use case, e.g., certain methods on the OpenGL widgets are described as thread-safe. Individual OpenGL contexts are also not thread-safe and must be used from a single thread at a time (and they usually involve some thread-local context).

The typical way you make a responsive UI is by doing only UI work in the UI thread, and passing off all long-running computations to background threads.


Well, I'd call that an insightful answer! Thanks!


Async functions are conceptually quite similar to ES6 generator functions. In an ES6 generator every `yield` expression is a point where the function can be paused and later resumed. With an async function it is an `await`. The difference between a generator and an async function is that whereas the caller manually resumes generators by calling (`next`) on the generator, async functions are automatically resumed when they yield a promise that is resolved.

Making a function support being paused and resumed requires changes in how the function is run and the data that is maintained while it is executing. In addition to behaving differently than sync functions when they execute, the results are also handled differently. Async functions always return a promise - whether they `return` a literal value, return another promise or throw an exception.

Due to these differences, it makes sense that the special nature of async functions and generators must be declared up-front, with the `async` keyword or `*` for generators. It would be possible to design the language such that the function type was determined by looking at whether it contains `yield` or `await` keywords. Python does this with generator functions. However this makes an important aspect of behavior less explicit.


To expand on the link between generators and async functions, the way that async functions were made to run in old browsers that didn't support them by tools like Babel and TypeScript was to rewrite the async function in two stages:

1. Rewrite the async function to be a generator function with a wrapper that calls `next` on the generator whenever `await`-ed Promises resolve

2. Rewrite the generator function to be a big switch statement, together with a wrapper function that drives execution.

You can play around with generators and async functions in the TypeScript playground, with TSConfig set to target ES5, to see how this works: https://www.typescriptlang.org/play?target=1#code/GYVwdgxgLg...


> Making a function support being paused and resumed requires changes in how the function is run and the data that is maintained while it is executing. In addition to behaving differently than sync functions when they execute, the results are also handled differently.

Can you expand on what the actual differences are?

That starts to make sense to me: normal functions/subroutines will execute from start to finish, while these async functions (coroutines?) can be paused right in the middle of its execution. This is something fundamentally different then, which could explain why we need the `async` keyword, because those "asynchronous" functions are special.

I'd be interested to know the actual differences.


Since you mentioned C# and most replies so far are about Javascript -

In C#, you can't use the "await" keyword, but you can use the result of a Task<T> in a non-async function with ".Result" or ".GetAwaiter().GetResult()". https://stackoverflow.com/a/47648318/5107208


Unfortunately, there are problems with both of those approaches: https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/b...

I don't know if it extends to other language implementations, but C# does not seem to have a reliable sync-over-async story.


Seems pretty standard to me. Every platform has problems just like this, nothing new there afaik.


Note that it's recommend that you avoid using these because they carry a risk of deadlock. Unless something has changed recently .Net doesn't have a 100% safe way to synchronously call an async method from a non-async method.


I mean, depends on what you mean by "100% safe". You can synchronously wait from a thread that's not in the thread pool executing the tasks. Obviously that's not sufficient for safety, but it should be illustrative that safety is possible. There are also some issues with using .Wait or .Result but those can be solved.


Can you elaborate on why there's a risk of a deadlock?


The very short version is that by synchronously blocking on the async method your thread may end up holding a resource (thread context info or the calling thread itself) that the async method needs in order to run its continuations. Here are a couple of more detailed explanations:

https://blog.stephencleary.com/2012/07/dont-block-on-async-c...

https://devblogs.microsoft.com/pfxteam/await-and-ui-and-dead...


Now that's pretty much the answer I was looking for, I believe. This makes things much clearer!


It's conceptually a different thing to "await" and to "stop execution till the thing I am waiting for happens"

In the first case I am telling an executor what it has to do (it has to execute the thing then continue inside my function)

In the second case I am myself blocked into a state that will be unblocked when the thing returns.

That is in the first case my function must be of a type that can be stopped and continued and I need to save its state somewhere.

In the second case I don't need the ability to stop and continue my function.

That makes for two kinds of functions at some level. Low level languages will expose that. High level languages will hide it.

But there is a growing request for functions that can be both. I think that zig has them


> That is in the first case my function must be of a type that can be stopped and continued and I need to save its state somewhere.

And that state is saved in the heap, I presume? Can you provide a concrete example perhaps?

I get that the state in case of green threads (Goroutines for example) is saved in the call stack itself. Where all this is saved in async/await?


The typical implementation of async/await need to store (the equivalent of) a single activation frame (as opposed to a stack of activation frames). This is normally stored separately on the heap, but it can be embedded in some other (heap allocated object) or even stored directly on the stack of some thread, depending on language, usage patterns and optimization capabilites.


the requirements of the second case are a subset of the first case, so then it should be theoretically possible to await inside non async functions. Seems to me that JS simply doesn't implement this ability, but it could if it wanted to


> I've also read that await() basically preempts the currently running function. How does this work?

Well, I would say that it's the opposite of preemption. It's cooperative multitasking. Your async task is split into two tasks, with the await in the middle. When you call await, the first task finishes. The second task starts running once the await is done.

> Why can't non-async functions use await()?

In C#, they can. You can call `.RunSynchronously()` on a `Task`. C# supports lots of different TaskSchedulers that handle this differently.

C# is almost pathologically flexible here. Other languages typically assume that there is only one async task scheduler, and it's handled by the runtime. This task scheduler may be less flexible, and there are various design tradeoffs.


Non-async functions don't have the features that enable them to be split into multiple functions that represent all the parts between the await()'s. That functionality is expensive in terms of time and space so that's why all functions aren't async.

When you call await the function doesn't necessarily have to be preempted (maybe the async function you called already returned) but if the async function hasn't returned then it has to be.

In most systems like that there is a scheduler that keeps a list of things that are being awaited on and keeps track of which ones are ready to return. When you call await on one function the scheduler looks at that list and chooses an await that is ready to continue and executes it.


If you could await in a regular function then that function would need to return a promise. But it would be confusing if adding an await keyword to the middle of a function body implicitly changed it into returning a promise. Thus we enforce that you have to use the async keyword on the function in order to enable this functionality.


I understand where you're coming from. But, how would this be any more confusing than the fact that using yield instead of return in Python turns a function into a generator? That just seems like a pretty thin justification for not allowing await without async.


I mean JavaScript makes the same decision here as well. To use yield you must define a generator using "function*" rather than a function using "function" exactly because it's confusing. Is it really so hard to to type `async`?


That's not a justification. Maybe JS used the same flawed reasoning?


> If you could await in a regular function then that function would need to return a promise.

Why would it have to return a promise?


Async/await requires a run loop. Async functions are special functions that can suspend execution and jump back to the run loop, and at some later point the run loop continues them.

Normal functions can't do that. If they wait for something they block, they can't give control back to the run loop.


So async functions are special functions that have the capability to be _paused_ until another asynchronous operation finishes (i.e. the thing we're `await`ing upon).

And in a single-threaded environment, we _want_ this capability so that the loop is able to continue working despite the fact that the thing we're `await`ing for hasn't complete yet.


There are a few different nuances to this question.

Can it be done? sure:

   let bar = await foo();
   return bar + 1;
can be converted to:

   let resultPromise = foo();
   while (!resultPromise.isComplete()){
       sleep(10);
   }
   let bar = resultPromise.value();
   return bar + 1;
the issue is that this is terribly inefficient as you are asking every 10ms whether or not the result is ready.

One of the reasons this is so inefficient is that you have to spend time checking (which is wasteful if it isnt ready) and waiting. If you check more frequently, you're wasting CPU. If you spend more time waiting, you can waste up to that much time if the result is ready right after you go to sleep.

The alternative is to have the operating system tell you when its ready. This is done through the event loop. So this would turn the code transformation into something like the following:

  let resultPromise = foo().then(function(bar){
     return bar + 1;
  });
  
but what do you return? you have to return a promise (i.e. a callback). Therefore, all callers of this function have to be ready to handle this.


> can be converted to:

Not necessarily! In a typical eventloop system - like with libuv - events and readiness will be checked within the eventloop while no user code is executed. The eventloop runs something along:

    while (true) {
        waitForOsEventsOrTimers(); // Uses epoll & co
        executeAllReadyCallbacks();
    }
Your whole promise code is part of `executeAllReadyCallbacks`. Thereby any blocking there will block fetching new readiness events, and thereby the code will be stuck.

It would only work if the polling OS functions would run on a different thread - which some runtimes like .NET might actually do. However its very often avoided since its less efficient than polling events inline without synchronization, and in Javascript wouldn't work.

When you now use the c


I don't know of a JavaScript implementation where that approach would work. A synchronous implementation of `sleep` can't yield control back to the main thread to handle the asynchronous stuff. You'd just end up polling indefinitely.


In JS, Python and similarly built systems async is inherently coupled to an inversion of control.

When you write (Python):

    await sock.write("fooooooo")
write will, after several layers of abstractions and wrappers, look like this (on a reactor/readiness-oriented system):

    def write(self, data):
        while data:
            try:
                written = self._socket.write(data)
            except EWOULDBLOCK, EAGAIN:
                yield 'writable', self._socket
            data = data[written:]
"yield" here means to pass a bit of data up to the event loop (neé coroutine scheduler), and that event loop will watch (using select/epoll) for that socket to become writable and then hand control back.

Because this inversion of control doesn't happen unless you (usually) explicitly wrap a coroutine (e.g. asyncio.run_until_complete) with an event loop, you can't just call a coroutine. It doesn't know when to resume in these designs, and your synchronous function doesn't know how to, either.


First of all, thanks for all the answers everyone. So, for the case of JavaScript running in the browser (single-threaded and all), is my understanding correct that:

1. For something to be `await`ed on, it means that the calling function has to have the ability to be paused and continued later 2. Non-async functions cannot be paused right in the middle and resumed later. They're regular functions that will execute from start to finish by the main thread 3. In a hypothetical JavaScript implementation where a non-async function could `await`, the main thread would not be able to pause the calling function but instead it would keep executing it, blocking the thread, because there would be noone to execute that function that we would be `await`ing on (since there's only 1 thread and it's blocked). So it's a deadlock.

So, the difference between async and non-async functions in JavaScript, is that the former can be paused in specific points (pointed to by `await`).


I think it was a good question, seeing all the various answers people gave trying to explain and clarify. I enjoyed the discussion, and it helped deepen my understanding.

As you said in another comment, what's confusing is that "await" actually means "Don't wait for the result, continue running other instructions, then come back to resume when the result is ready."

> Async operations like promises are put into an event queue, which runs after the main thread has finished processing so that they do not block subsequent JavaScript code from running. The queued operations will complete as soon as possible then return their results to the JavaScript environment.

From: General asynchronous programming concepts - https://developer.mozilla.org/en-US/docs/Learn/JavaScript/As...

---

About how async functions can be "paused and continued", it relates to the general concept of "continuations", which are:

> ..constructs that give a programming language the ability to save the execution state at any point and return to that point at a later point in the program, possibly multiple times.

> ..In C#, async and await "sign up the rest of method as the continuation, and then return to your caller immediately; the task will invoke the continuation when it completes."

https://en.wikipedia.org/wiki/Continuation


> Why can't non-async functions use await()?

To await means to wait for a Promise to resolve asynchronously. That's why async functions return a Promise instance.

  async function asyncFn() {
    const result = await fetch(url)
    const json = await result.json()
    return json
  }
  
  asyncFn().then(json => console.log('Got', json))
  console.log('Fetching result..')
Calling asyncFn() will immediately return a Promise instance. Then each await in the function waits for a Promise to resolve, before continuing to the next instruction. While that's happening, other code can keep running, like the last line in the example above.

In contrast, a non-async function is synchronous, which means it's expected to run all instructions one after the other, then return a value. No other code can run while that's happening.

If await was allowed in a non-async function, it would have to block execution of all other code until the Promise is resolved/rejected.

  function syncFn() {
    const result = await fetch(url)  // Stop the world
    const json = await result.json() // Stop the world
    return json
  }
  
  console.log('Fetching result..')
  console.log('Got', syncFn())
This is how a synchronous HTTP request works: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequ...

To avoid stopping the world, a non-async function can use Promise callbacks instead.

  function promiseFn() {
    return fetch(url)
      .then(result => result.json())
  }
  
  promiseFn().then(json => console.log('Got', json))
  console.log('Fetching result..')


Yes, but if I am ok with stopping the world, why doesn't JS allow me to do so? Why do we need to have an entirely seperate api for sync http calls when allowing awaiting inside sync functions would have done the same thing?


Because JS is single-threaded¹ and is designed for use on the web. If you block that thread, then all event processing ceases—button clicks don't register, scroll events don't fire, etc. So all JS API's are designed to be non-blocking.

¹Not really, but close enough.

Originally, this was resolved via callbacks:

  doSomething((err, result) => {
    if (err) { ...failure code... }
    ...success code...
  }
But that got very clunky, resulting in something called "callback hell" and the "callback pyramid of doom." So promises were introduced:

  doSomething().then((result) => {
    ...success code...
  }).catch((err) => {
    ...failure code...
  });
But that's also clunky, so the async/await syntax was introduced:

  try {
    let result = await doSomething();
    ...success code...
  }
  catch (err) {
    ...failure code...
  }
And that's actually quite pleasant to use.


I haven't tried it and surely not recommended, but I think it's possible to block execution with a while loop until the promise is resolved/rejected. This will be like a "synchronous await", which defeats the purpose of having async functions.

  function syncFn() {

    let result
    let state = 'pending'

    asyncFn()
      .then(data => {
        result = data
        state = 'fulfilled'
      })
      .catch(error => {
        result = error
        state = 'rejected'
      })

    while(state==='pending') {} // Stop the world

    return result
  }

  console.log('Result', syncFn())


I don't think this will work in JavaScript because that while loop will block the event loop and prevents callback from executing. For example, the following code will be blocked when running in Node.js:

    function delayedValue(val, ms) {
        return new Promise(resolve => setTimeout(() => resolve(val), ms))
    }
    
    function syncFn() {
        let result
        let state = 'pending'
    
        delayedValue(1234, 500)
            .then(data => {
                result = data
                state = 'fulfilled'
            })
            .catch(error => {
                result = error
                state = 'rejected'
            })
        while (state == 'pending') {}
        return result
    }
    console.log('before')
    console.log(syncFn())
    console.log('after')


JS does - just wrap all your code in an async function, and then you can await within.

> async function run() {

    // call your program logic here

    await myCode()
}

> run()


But this approach would async-pollute all the sync calling functions right? if I had a call tree like

  function A(){ B()}

  function B(){ C()}

  ...

  function Y(){ await? Z()}

  async function Z(){...}
Then A-Y would need to become async. If await Z() was allowed then we could avoid this. So is there a reason why it could not be implemented this way?


Is it true that async/await is syntactic sugar over Promises, and that Promises are sugar over callbacks?

If that's true, does the "function coloring" problem apply to Promise-based code and callback-based code too? (Talking about JavaScript here)


Pretty much... all you need to provide to `await` is an object with a 'thenable' function. Even this works:

  (async () => {
    const promise = {
      then (resolve) {
        resolve('World');
      }
    };
    console.log('Hello', await promise);
  })()


Yes, function coloring applies to them too. The function coloring problem is semantic, not syntactic (with the possible exception of rule 4 "Red functions are more painful to call"), so no amount of adding or removing syntactic sugar will affect it.


> I'm trying to wrap my head around async/await and how it works under the hood, be it JavaScript, C# or others.

Careful, it doesn't work the same way in every single language that has these keywords.

> Why can't non-async functions use await()?

Because in Javascript at least, it only makes sense to await a promise, nothing more. A function marked as async will always return a promise. You can't await on anything that is not a promise.


> A function marked as async will always return a promise.

I understand that async functions return promises, but why can't you await for a promise from a non-async function? For me it would seem logical since by awaiting you essentially turn a promise into a concrete result which you can then use in your non-async code.


My thoughts exactly.


I think this is technically incorrect.

It's perfectly valid to await on a literal:

  > async function test() {

    let a = "yep"; 
    await a; 
    console.log(a); 

  }

  > test()

  > yep


when you use `await a`, a is wrapped in a promise. Try returning await a - you get a promise and not the literal. In your example you don't use the promise, and a is a literal.

> async function test() {

  let a = "yep"; 
  return await a; 
}

> let b = test()

> console.log(b)

> Promise { 'yep', [Symbol(async_id_symbol)]: 1215, [Symbol(trigger_async_id_symbol)]: 5, [Symbol(destroyed)]: { destroyed: false } }

The promise you create with `await a` resolves immediately (equivalent to Promise.resolve(a)), but it's still a promise.


This isn't _exactly_ true. JS will await on anything that is Promiselike (an object with a `then` function that accepts a callback). See the TS definition of Promiselike.


JS will await on literals too, it just immediately resolves to the value of the literal.

I use this all the time in async functions with code branches, some of which await and some return an immediate value.


In JavaScript, "await" is syntactic sugar for promises.

  async function myFunction() {
    a();
    await b();
    let d = c();
    return d;
  }
becomes something like this:

  function myFunction() {
    return new Promise((resolve, reject) => {
      a();
      b().then(() => {
        try {
          let d = c();
          resolve(d);
        }
        catch (err) {
          reject(err);
        }
      }).catch(reject);
    });
  }
If you didn't mark `myFunction()` as async, it wouldn't set up the `new Promise()` scaffolding, and there would be no `resolve()` or `reject()` to call.

Note that you can call an async function from a non-async function. Assuming b() is async and a() and c() are not, a manual approach looks like this:

  function myFunction(callback)
    a();
    b().then(() => {
      try {
        let d = c();
        return callback(null, d);
      }
      catch (err) {
        return callback(err);
      }
    }).catch(callback);
  }


There's been a lot of discussion here of the technical reasons for function coloring, but the semantic reasons for it are far more interesting and important to me. I suspect that technical limitations could (and in some languages do) have workarounds, but there's real value to the system as it's set up.

When I mark a function as `async`, I'm specifying that this function will be performing some form of asynchronous I/O. Any caller of that function (or its callers' callers) will therefore also be doing I/O. Such functions may take a while to "return" and are more likely to throw an exception.

Function coloring makes these properties explicit. By forcing all callers to also be `async`, it becomes easy to see which parts of the codebase are doing (somewhat risky) I/O work and which parts will be near-instant. No one ends up surprised when a call to `getFoo()` ends up getting `foo` from the network rather than the local object.


But async doesn't imply IO in many languages, it could just be a timer or a cross thread operation. At the limit is just lazy evaluation.

Similarly the absence of async doesn't guarantee the absence of IO, the function could be doing plain old sync IO or scheduling continuations on the event loop by hand.

IMHO there isn't any justification for async in "high overhead" languages, i.e. most of the dynamic languages that are not going to spawn tens if thousands of concurrent coroutines; those languages are better served by green threads or some sort of stackful continuations (the existence of async in python is particularly baffling).

It is more of a necessary evil in high performance languages were dedicating a call stack is a comparatively large cost. Even then I strongly believe that async functions should be awaited by default and any forking should be explicit.

YMMV.


Because the sync function does not know what to do in case the async function doesn't return immediately and awaits something else. It can't block because that would block the whole thread, possibly including UI. Spawning a new thread and blocking there would break the whole point of async and would be expensive.


So it's not that it _can't_ block while `await`ing, it's that it doesn't _want_ to block?


What helped me understand async await concept was to understand the concept of generators in javascript. One quick way to do it was to implement generator using ES5 javascript.

From generator then it was to see on how to implement async await using them. This was how 1.x version of koajs did (https://github.com/koajs/koa/blob/master/docs/migration.md).

Another way was to understand how polyfill for an implementation works. eg. https://github.com/facebook/regenerator


It may be better to ask yourself what does await do?

I am no longer familiar enough with C#, so I will take NodeJS as an example.

In NodeJS everything that you do happens on one thread, which is great because you will never, ever, need to worry about locks and threads.

The downside is that you can only serve one client at a time. To avoid this, whenever you start reading a file, or send a network package, or something else that takes a long time where you are not doing anything, Node stops running your code and runs some other code for a while. Then it will come back to you when it has a result and start running the code again.

It is easy to understand for us what come back means, but how to express that in an language? First attempt was with callbacks - node would come back to whatever you put in the callback. The downside of this is that you get very nested code, you have to repeat code to handle errors and it becomes a mess fast, especially if you have to deal with collections.

The next attempts are promises. These are a lot easier to deal with than callbacks, because you get some value back than you can treat like just another object (put in containers, etc) but in practice your code still ends up having to stop and pass values through functions all the time, which puts in visual noise and is a pain to deal with.

Next step then is async/await. Instead of coming back by executing a function, you use the keyword 'await' to tell the compiler where to await to. As for why this can't be used inside a function, await is a legal identifier in plain Javascript - await(someValue) - has a very different meaning if you have your function marked as async vs if you don't.

I use await a lot at work, and my mental model is just that it is the same as promises, except that instead of having to write an annoymous function, invent a new name for the result of the promise and showel my code into the anonymous function, the compiler does so for me.

Thus I get to write:

    let a = await fs.readFile....
    let dirContent = await fs.readDir(...
    return dirContent.contains(a)
Instead of:

    return fs.readFile....then(fileContent => fs.readDir(...).then(dirContent => dirContent.contains(a)))
Which is, to me, borderline unreadable.


> It may be better to ask yourself what does await do?

Yes, probably that's something that slips me too.

> It is easy to understand for us what come back means, but how to express that in an language? First attempt was with callbacks - node would come back to whatever you put in the callback.

I feel like I have to even understand what NodeJS does when you give it a callback function. So, how the asynchronous model works in a pre-Promise, pre-async/await world? And then when the loop is notified from epoll that a response came back, it goes and executes that callback function with the data?

Is that why they say that in async, the state is spilled all over the heap? Because the original function that issued the HTTP request and provided the callback, returned immediattely, and that callback function had to be allocated somewhere on the heap so that it could be executed eventually when that I/O was complete?

Does NodeJS uses epoll to ask to be notified when a response from a TCP socket comes in, and then puts the callback function in some kind of internal queue (the one that the event loop consumes from?)?


I'll take a stab:

In JavaScript, you only have one thread. You really don't want to block it for any length of time. IO tends to take time. So IO is always async. There's no way to turn something async into something synchronous without blocking your thread waiting.

Other languages do let you block a thread to wait for an async task to complete, JavaScript doesn't mostly because there is only one thread.


I see, so the asynchronous model that JavaScript implements is an answer to the question:

> Given a single thread, how can we do multiple things at once (slow I/O, process user events like a mouse click etc.)

And the answer to this, is that we use continuations. Is that correct?


Yep, and even in languages with multiple (OS) threads, a thread is still a resource with a stack that takes some memory, and so they might also do non-blocking IO with either with callbacks or promises or similar.


What are you "awaiting" in a sync function? There's nothing to wait for, at the very least it may be you are looking for similar under-the-hood functionality of splitting functions, but even then you would name it something else. Regular functions just execute as expected, and splitting them would have no real effect on overall performance.


I'm awaiting for the promise of the function I'm calling to resolve, similar to what I do in an async function. Why is there nothing to wait for?

Actually I don't understand why the `async` keyword is needed at all.


If you want your function to block until the promise resolves, then the function calling it also has to block, and the function calling that has to block, and so forth.

At the top of the chain, this ultimately blocks the entire event loop (Javascript semantics are generally not concurrent), so no UI/network events can be processed until that promise resolves and the page/server is left non-responsive.

(And that's assuming you can somehow define clear semantics to run any Javascript code involved in resolving the promise; otherwise, you're deadlocked!)


If you have one thread, and it's just in a loop checking and rechecking on a promise to resolve, you will deadlock, because nothing is actually running the async function.

Await actually pauses your function and allows the async runtime to work on other stuff, such as completing the function you just awaited on.


If you're waiting, it's not synchronous by definition. It's already an asynchronous function.


This is kind of turning the classical definitions on their head.

Synchronous I/O functions don't rerurn until the I/O is complete (or complete enough, anyway). The calling function effectively waits for completion.

Asynchronous I/O functions return right away, but completion is signaled elsewhere. The calling function does not have to wait (but it can choose to wait for completion if necessary). Asynchronous messaging may not even provide a completion signal, if you want to know it got there, the other side needs to send an asynchronous message back to confirm.

Await/async is confusing enough without redefining terms to mean their opposites.


Yes, exactly what I was thinking. I think that I was confused in the first place because `await` I thought meant "block until this function returns". Whereas it really means: "don't wait, swap me out".


Yeah, I think a lot of that is the interface isn't great/the abstraction is leaky and you need to know a lot about the details. Because implementations of async/await seem to usually rely on some sort of thread sharing, await also implies yield, and there's often no or limited preemption. Of course, in Javascript, you don't usually have multiple threads at all (unless you fit into one of the special exemptions that allow multiple threads), so your options are pretty limited.


> Actually I don't understand why the `async` keyword is needed at all.

Indeed:

    $ raku -e'sub non-async { await start { say "hello world" } }; non-async'
    hello world


At least in python it is needed so that the interpreter can issue very cryptic syntax errors.

No, I'm not bitter.


you may find hyperscript interesting:

https://hyperscript.org/

hyperscript does away with the distinction between sync and async code by moving it all to the runtime:

https://hyperscript.org/docs/#async


JavaScript modules can use top-level `await`, you don't need a function.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...


Bob Nystrom does a good explaining this problem, and how it could be avoided: http://journal.stuffwithstuff.com/2015/02/01/what-color-is-y...


I wrote this blog post a while back on the basics of async functions in Javascript, might be helpful

https://medium.com/front-end-weekly/callbacks-promises-and-a...



just remember it is just sugar:

const a = async () => "hello world"

is the same as

const b = () => Promise.resolve("hello world")


Shoot me an email if you want to talk about it. At least the JavaScript version I'm very familiar with. It doesn't match 1 to 1 with stuff like C# though. Email in profile.


Incidentally what is the performance benefit of the whole promise/callback hell compared to GOTO? I find it needlessly confusing and inhuman.


There's no performance benefit. Promises/callbacks are abstractions which live in a much higher level programming space.

Incidentally, many high level programming languages _don't_ include GOTO, because people find it needlessly confusing and inhuman.


both are terrible concepts. Languages are supposed to help humans, or else we should all write assembly.


Summary: Explicit 'async' functions avoid implicit messiness and allow control over which sections of code are running at one time.

> From what I understand, in JavaScript at least, putting `await foo()` inside an async function, splits the calling function in two, with the 2nd half being converted to a callback

I wouldn't say that's wrong, but it may be a slight over-simplification if your mental-model isn't complete. For example you can await from within a loop which would make function-splitting kind of a strange thing to think about. But yes, every `await` is a suspension point that will be resumed and the function carries on, so effectively the rest of the function becomes a callback, but let's call it a "continuation", which is the callback that continues from where we left off.

Of course it's implementation specific, but at a high level these continuations are basically normal functions which may return either the "incomplete" pair (new_awaitable, next_continuation) for `await new_awaitable` statements, or the "complete" return-value for `return` statements. (Maybe other things like exceptions, but let's not go there)

It's the event-loop/task-scheduler which keeps track of these callback/continuations: when one returns with a complete-value, the scheduler will go find the continuation that was awaiting it, and call that with the new value. If it returns a new (awaitable, continuation) object, the loop will start the awaitable and repeat until it returns a value, then resume the continuation with the value.

So you can think of await as "return to the event loop with awaitable and keep calling the callbacks until a final value is returned".

This is where the function coloring comes from: async functions need access to that event loop to keep returning to and continuing from, they know they're being called in a special way and return these special values for await/return that the event loop knows how to deal with; normal functions don't have access and just return values in the "normal" way.

Of course, to reap any benefit from this you have to have multiple tasks running "at the same time". To just await one thing which awaits one thing is not much different than a normal function call/stack. But when you're listening on sockets and fetching HTTP resources and making database queries and waiting for user responses, each executing simultaneously but on one thread (so no thread contention), that's when async shines. But note: they all must be talking to the same event loop which manages all these coroutines, running one at a time.

But what happens if "normal" functions could await?

Put another way: effectively de-color functions by making them all "async" and thus able to await. (Unless I'm misinterpreting the initial question)

From what I said above, all we have to do is change the implementation such that when a function returns, internally it actually this special "complete" variable type (with the real returned value inside). Then, with an event loop you get normal behavior and forward to whatever function had called it. Then you just add the awaits which return "incomplete" variables and you get waiting behavior.

This would work, BUT it introduces an overhead of going to the event loop checking if the return is "complete", finding the caller, and forwarding it for EVERY function call. Rather than the usual stack popping/register storing of standard languages. Optimizable? yeah probably, but it's not zero-cost, especially true in languages like Python which doesn't have an implicit event loop like JavaScript, so you're making huge changes to the implementation to begin with.

We could approach from the other side: returns stay the same, but the act of calling await creates an event loop or some object which does the "callback-the-callbacks" routine. I guess this would be the stop-the-world approach? This one function would keep looping through the callbacks until a final value reached. On second thought, this isn't different than a standard function call, because any internal `awaits` would setup their own loops and you always end up with a single returned value (or infinite loop). There's no way for multiple loops to running the continuations.

If you really don't like the keyword "async" before your function definition you could implement implicit async functions: just make any function in your language which includes an `await` implicitly become "async". But this only kicks the can down the road, as if you use await, that means you're returning continuations, and any callers need to await your value, and therefore your function is now implicitly colored.

And how do you implement the equivalent of

    async function() { return 42; }
maybe?

    function() { return 42; await; }

Implicit event loop.

Maybe this is the core of the question: for a language like JavaScript which already has an event loop, why can't we do

    function foo() { let x = await whatever(); return x; }
    console.log(foo());
The await in a non-async function could be implemented such that it goes out to the global event loop, "registers" itself as dependent on the Promise returned from "whatever()" then sit and wait for the event loop to return the value. This should sound familiar to you as the behavior of the standard async function / continuation business, but now we're trying to say that foo doesn't return a stream of continuations, (it just takes a long time to run).

While this may be doable, a problem arises if foo was called from within an async function. Part of "contract" of async programming is your function is run as usual between calls to await; at those points you return to the event loop and let other continuations run, but until the next await, shared/global variables will remain untouched because nobody else is running. If you called foo() which then starts running `whatever` on the event loop again any guarantees about ordering of events or state of variables may change unexpectedly.


> Maybe this is the core of the question: for a language like JavaScript which already has an event loop, why can't we do

    function foo() { let x = await whatever(); return x; }
    console.log(foo());
Yes, that's one of my main questions. What would happen if we did this? It's my understanding now, after reading a lot of answers (yours included), that in JS land this would result in a deadlock, because the event loop would be waiting for something (whatever()) that would itself be waiting for something else.


There are two options for doing things in JavaScript: 1) do it now, or 2) start it now and get back to me when you're done.

JavaScript makes (1) the default. For any kind of object access (dog.legs[1].paw), assignment (const q = 5), math (Math.floor(4.2)), or DOM access in a browser (document.write('yay')), the interpreter will run each statement in sequence, completing each one before running the next.

Historically, the way ask the interpreter to "get back to me when you're done" was to use the "callback pattern," which was little more than passing in a function:

  setTimeout(4000, () => console.log('second'));
  setTimeout(5000, () => console.log('third'));
  setTimeout(3000, () => console.log('first'));
When it came time to construct sequential-looking programs that involved lots of these calls, things could quickly get messy:

  http.get(catalogUrl, (err, catalog) => {
    if(err) {
      handleError(err);
    }
    http.get(catalog.products[0].url, (err2, product) => {
      if(err) {
        handleError(err);
      }
      document.write(product.name);
    });
  });
Promises flipped this callback style into an object. Async/await morphed those promise objects and methods into language syntax that looks like one thing happening after another:

  catalog = await fetch(catalogUrl).catch(handleError)
  product = await fetch(catalog.products[0].url).catch(handleError)
  document.write(product.name)
If the original question were "why are some APIs async and some not?" the answer would be: "JavaScript engine authors (web browsers, node) have decided that some things like network access are inherently too slow to pretend they happen instantly, and waiting on them would mean that the UI would have to become unresponsive, which would make users think something is broken. Other APIs like DOM access are fast enough to get away with freezing the UI and nobody notices."

But the question was, "why can't you call an async function from a synchronous (non-async) function?" The answer there is more like, "Because synchronous functions are expected to return a result without waiting, and if you could call an async function but pretend you didn't, there's not a good solution for what should happen in composition with other non-async functions. Would you proceed without having a result? Would you force a wait anyways? None of those are acceptable answers because they break the model of 1) some things are nearly instantaneous and 2) some things take longer. We still need a mechanism to work with things that take a long or unknown amount of time, without making the UI freeze."

As lolinder points out in a sibling comment, this decision to split things into 1) fast and 2) not fast was ultimately a semantic decision of JavaScript engine/API designers. The downside of this decision is that it takes programmers some time to be comfortable with the unintuitive model. The upside is that it enables programs to be written by (fallible, mortal) programmers and not freeze up all the time.


> I've also read that await() basically preempts the currently running function. How does this work?

So async/await work by having the compiler do a mechanical transformation of the function. If you have code that looks like this:

  async function foo() {
    let x = await bar();
    await baz(x);
  }
The actual code that gets generated is something like this (this is off the top of my head, forgive any transcription errors):

  function foo() {
    let x = undefined;
    return Promise.resolve(null)
      .then(() => bar())
      .then(function (val) {
        x = val;
        return baz(x);
      });
  }
In other words, the async and await keywords work together to tell the compiler how to properly transform the function into the necessary async API: the async keyword says that the function needs this mangling, and the await keyword tells the compiler where the possible re-entry point is.

So, with that in mind, let's answer your main question:

> Why can't non-async functions use await()?

The shortest answer is that the semantics of doing the necessary transformation to support await in a function which doesn't opt into that transformation are hard to design and even harder to use.

Fundamentally, adding async to a function changes the API of the function [1]. In JS, it causes a function to return Promise<T> instead of T, and this API change can be done even without having any await keyword in the body of the function. Similar changes happen for other languages, just substitute Promise for whatever variant of will-resolve-in-future API is in that language.

In principle, it's possible to support such a scenario by making such an await just "run" the promise to completion. But here you run into issues with how the runtime handles asynchronous tasks. Is it possible to spin a nested event loop? Not necessarily, it depends on the runtime. If your synchronous call is already on a task (so there's basically an async function on the callstack), you would really want to basically inline all of the function calls so that the await causes the same transformation were it to be called directly in that async function. But such inlining is impossible (due to dynamic function calls).

To really delve deep into the details here requires a fuller understanding of how asynchronous runtimes work in general, and of the particulars of the various implementations. Async/await is a wonderful innovation in that it allows programmers to write asynchronous code state machines as if they were generally writing synchronous code. But it doesn't do anything to change the deeper issues of how the task runtime system actually works, and in particular, the "correct" way to execute an asynchronous task in a synchronous context, which has multiple answers that are not wrong.

[1] The fact that async functions have a fundamentally different API and ABI makes the "what color is your function" argument somewhat specious: these functions have color because they're different, and you have to be aware of the difference. Only by basically forbidding async (or, in some languages, sync) functions entirely can you make the difference appear to go away, but you'll notice that that doesn't exactly "solve" the issue.


> In principle, it's possible to support such a scenario by making such an await just "run" the promise to completion. But here you run into issues with how the runtime handles asynchronous tasks. Is it possible to spin a nested event loop? Not necessarily, it depends on the runtime. If your synchronous call is already on a task (so there's basically an async function on the callstack), you would really want to basically inline all of the function calls so that the await causes the same transformation were it to be called directly in that async function. But such inlining is impossible (due to dynamic function calls).

I feel like this touches the point that I'm trying to grasp currently. So, do I understand correctly that in JS land, calling `await` from a non-async function and making that await block until that promise was resolved, would result in a deadlock, because JS is single-threaded?

Why do we need a "nested event loop"? Is it because (a) the non-async caller blocks the only thread, and so (b) there's noone else in order to fulfill that promise being awaited on?




Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: