Search code examples
javascriptasynchronouspromiseasync-awaites6-promise

JS Promises: Why does await have to be inside an async function?


Say I have the following code:

new Promise(res => res(1))
.then(val => console.log(val))

I can achieve the same thing with async/await like this:

let func = async () => {
  let val = await new Promise(res => res(1))
  console.log (val)
}
func()

I put the async/await code inside a function only because you have to be inside an async function in order to use await.

What I Want To Know: Why is this rule enforced? What would be the problem with just doing

let val = await new Promise(res => res(1))
console.log (val)

Is the reason that await causes the current scope to pause execution, and so forcing you to put the async/await code inside the special scope of an async function prevents JS from pausing the execution of all the rest of your code?


Solution

  • An async function is a different kind of function. It ALWAYS returns a promise. At the point of the first await that it hits, the function execution is suspended, the async function returns that promise and the caller gets the promise and keeps executing whatever code comes next.

    In addition, the async function automatically wraps its function body in a try/catch so that any exceptions whether synchronous or unhandled rejected promises from an await are automatically caught by the async function and turned into a rejection of the promise they automatically return.

    And, when you return a value from the async function, that return value becomes the resolved value of the promise that it returns.

    What I Want To Know: Why is this rule enforced? What would be the problem with just doing...

    An async function has a number of behaviors that a regular function doesn't and the JS interpreter wants to know ahead of time which type of function it is so it can execute it properly with the right type of behavior.

    I suppose it might have been possible for the interpreter to have discovered when compiling the function body that it contains an await and automatically given the surrounding function an async behavior, but that's not very declarative and simply adding or removing one await could change how the function works entirely. Just my guess here, but the language designers decided it was much better to force an async function to be declared that way, rather than infer its behavior based on the contents of the function body.

    The big picture here is to understand that an async function just works differently:

    1. Always returns a promise
    2. Automatically catches exceptions or rejected awaits and turns them into rejections
    3. Suspends function execution upon await
    4. Converts returned value into resolved value of the promise it returns
    5. Chains an explicitly returned promise to the async-returned promise.

    And, as such the language is clearer and more declarative if that separate behavior is spelled-out in the declaration with the async keyword rather than inferred from the function body.