24

One has to use async keyword on the containing function to use the await inside the function body.

async function fetchMovies() {
  const response = await fetch('/movies');
  console.log(response);
}

fetchMovies();

The await is being used to block on completion of the asynchronous fetch() call. As can be seen in the code, the function fetchMovies() is not even returning any value. And even if it did, it would affect the way the return value is consumed by the caller, but why should it matter to the call to another asynchronous call from the function body?

My question is why is this required? Is there some good explaination of that? Is it related to the need for actual implementation of await and supporting it in older JavaScript versions?

I know iffi pattern is used be able to use await but does that change the semantics for the code that follows the iffi code block in any way?

(async () => {
  const response = await fetch('/movies');
  console.log(response);
})();

I am also aware of top level await being supported in modules.

May be I am missing something really obvious.

Machavity
  • 28,730
  • 25
  • 78
  • 91
Sandip Chitale
  • 391
  • 3
  • 8

4 Answers4

34

There are three reasons the async keyword exists:

  1. In ECMAScript language versions prior to 2015, await was not a keyword. Marking a function async provides a syntactic "bailout" to indicate a breaking change in the language grammar within the body of the function.

    This is the most important reason. Without the async keyword, all programs written in ECMAScript 5 or older would no longer work if they used the await keyword as a variable (in fact this was done intentionally in some cases as a polyfill before async/await was standardized), since that would cause a breaking change without the addition of async to the specification. Because of this, async is syntactically necessary to avoid breaking changes to the language.

  2. It provides a convenient marker for parsers, avoiding an infinite look-ahead in order to determine whether or not a function is asynchronous.

    This makes parsing more efficient, which is appealing for both ECMAScript implementers and developers, though this reason alone does not make async strictly necessary to the syntax.

  3. async also performs its own transformation on the function, which is done regardless of whether or not the await keyword is present in the body.

    Consider the following two functions:

    function foo() {
      if (Math.random() < 0.5) {
        return 'return';
      } else {
        throw 'throw';
      }
    }
    
    async function bar() {
      if (Math.random() < 0.5) {
        return 'return';
      } else {
        throw 'throw';
      }
    }
    

    async performs the following transformation of function bar():

    function bar() {
      return new Promise((resolve, reject) => {
        try {
          resolve((/*async function bar*/() => {
            if (Math.random() < 0.5) {
              return 'return';
            } else {
              throw 'throw';
            }
          })());
        } catch (reason) {
          reject(reason);
        }
      });
    }
    

    Those familiar with promises will recognize that we can simplify the above since the Promise constructor executor function will implicitly reject if it throws an error synchronously:

    function bar() {
      return new Promise((resolve) => {
        if (Math.random() < 0.5) {
          return resolve('return');
        } else {
          throw 'throw';
        }
      });
    }
    
Patrick Roberts
  • 40,065
  • 5
  • 74
  • 116
  • 2
    Also, as I understand it, async/await as a syntax existed in C# a few years before it was in ES, and Python somewhere in-between. I wouldn't be surprised if that played an influence. Of course, that's begging the question a bit, as your reasons above surely apply to the predecessor languages as well. – TheRubberDuck Feb 09 '21 at 13:46
  • @TheRubberDuck you're correct to point out the precedent though, since an established precedent does give the TC39 committee additional consideration to bear when evaluating a language proposal. – Patrick Roberts Feb 09 '21 at 14:12
  • 1
    (And it's worth noting that C#'s arguments for an `async` keyword were exactly those listed above, as I understand it) – canton7 Feb 09 '21 at 14:49
  • I would add that it is also good syntax design. Without marking a function with `async` you are always forced to read every line of a function body to see if the function returns a promise or not. Yes, we still have this problem with regular promises but when creating new syntax why not make it better? – slebetman Feb 09 '21 at 22:00
2

I assume your exact question is this: "Handling return values (null or something) depends on the consumer who called the function. They "should" supposedly get it even if another asynchronous function is called in-between. So why does it matter to wait before further other asynchronous calls?"

You see, such fetch() calls are done in Databases within the duration of "initiating" and "closing" the connection. Almost every method used is asynchronous in this case. So while you're executing fetchMovies(); The thread might move further and execute connection.close(); before the fetching is resolved and returned.

The exact scenarios are like this:

await client.connect();  //connection establishment
// Your purposeful methods
async function fetchMovies() {
  const response = await fetch('/movies');
  console.log(response);
}

await fetchMovies();

// Closing connection to avoid attacks and maintain concurrency
await client.close();

If any method, in this case, is called in an asynchronous manner, the whole session of a Database connection is wasted and our function would return undefined or throw an error "Cannot use a session that has ended"

So we need to "wait" for the "Pending" Promises to reach a "Fulfilled" or "Rejected" state before executing further calls.

You can refer more to this article: Using Promises, async / await with MongoDB just for the sake of understanding.

2

I think it's to make it clear that the function contains asynchronous code. Let's use another example that does return something:

async function canUseGeolocation() {
  const result = await navigator.permissions.query({name: 'geolocation'});
  return result.state;
}

The async keyword tells the javascript engine that the function should return a promise, and any return statements in the function should resolve that promise. What happens if we modify it to cache the values so we don't always call the await?

function canUseGeolocation() {
  if (cachedPermissionState) return cachedPermissionState;
  const result = await navigator.permissions.query({name: 'geolocation'});
  cachedPermissionState = result.state;
  return result.state;
}

How should javascript know that the function should return a promise? Because it contains an await? What if you later change the function so that the cachedPermissionState is set elsewhere and this function only returns that value so you remove the await? Should that first return statement return a promise or return the value? That would now change the whole way the function is executed and what is returned by return cachedPermissionState;. By using async, we can know that it really returns a promise that the return statement resolves without scanning the function for await statements to determine if it should be treated as async or not.

Jason Goemaat
  • 27,053
  • 14
  • 78
  • 109
2

There are two questions being asked here. Why do you need the async keyword to indicate an async context, and why do you need async context to use await?

If we didn't use the async keyword to indicate an async context, then we would have backwards compatibility issues. Before the async/await update, "await" was a valid variable name. So to make it a reserved word, we need to introduce a new context, the async function context.

The second question is, why do we want this in the first place? Why do we want to differentiate async functions from traditional synchronous code? As the name implies, async functions do not execute all their code at once. When a function "awaits", it effectively stops execution, and the remainder of the code becomes an event handler for the thing being awaited. This allows other code to run.

Browser javascript implementations are single-threaded. Only one thing can be performed at a time, but the event queue allows functions to take turns. Consider what would happen if you could await from a synchronous function. The synchronous code, by definition, does not give up control. Therefore, the async function it's waiting for will never get a chance to swap in to your single execution thread, and you will be waiting forever. So, you can only await an async function if you are already in an async context.

nupanick
  • 341
  • 2
  • 8
  • 1
    You could easily get the same behaviour without the `async` keyword, simply by the parser locating the `await` keyword within it, without explicitly needing to declare the function as `async`. You explained why you can't return an async result from a sync function, you did not explain the necessity of the "`async`" keyword. – deceze Feb 09 '21 at 21:02
  • 1
    "*can be "swapped out" while other code runs*" sounds a bit like they automatically are processed in the background, or swapped out when other code *wants* to run - but that's wrong of course. Could you please rephrase that a bit to clarify that it only gets swapped out when it chooses to do so on its own, with `await` (when waiting for something asynchronous), so that other code *can* run instead of being blocked? – Bergi Feb 09 '21 at 23:35