21

Why does using await need its outer function to be declared async?

For example, why does this mongoose statement need the function it's in to return a promise?

async function middleware(hostname, done) {
  try {
    let team = await Teams.findOne({ hostnames: hostname.toLowerCase() }).exec();
    done(null, team);
  } catch (err) { done(err); }
}

I see the runtime/transpiler resolving the Teams promise to it's value and async signaling it "throws" rejected promises.

But try/catch "catches" those rejected promises, so why are async and await so tightly coupled?

temporary_user_name
  • 35,956
  • 47
  • 141
  • 220
Michael Cole
  • 15,473
  • 7
  • 79
  • 96
  • I don't get what you mean by "*But try/catch "catches" those rejected promises*". How is that relevant for the `async` keyword? – Bergi May 25 '17 at 20:15
  • Possible duplicate of [Why is it necessary to have the async keyword](https://stackoverflow.com/a/31485350/1048572)? – Bergi May 25 '17 at 20:19
  • "*why does this mongoose statement need the function it's in to return a promise?*" - how else would it be able to wait for the asynchronous result of the mongoose promise? – Bergi May 25 '17 at 20:20
  • Here's a reason for the language design: https://stackoverflow.com/a/41744179/1483977 – Michael Cole May 26 '17 at 21:06
  • It should have been enforced “Async all the way” like in C# but it was half baked into JS :( ... the way it was implemented in JS ends up just some text that coders learn they have to paste or it doesn't work – – kofifus Jun 02 '23 at 05:34

3 Answers3

9

I'm not privy to the JavaScript language design discussions, but I assume it's for the same reasons that the C# language requires async (also see my blog).

Namely:

  1. Backwards compatibility. If await was suddenly a new keyword everywhere, then any existing code using await as a variable name would break. Since await is a contextual keyword (activated by async), only code that intends to use await as a keyword will have await be a keyword.
  2. Easier to parse. async makes asynchronous code easier to parse for transpilers, browsers, tools, and humans.
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • 1
    I don't think either of these arguments explain why using `await` requires the function to be asynchronous. _easier to parse_ is **never** a good argument for language design. It has to be easier to learn and program, the parser is a program and should do the heavy lifting. Backwards compatibility could be fixed by using any keyword that prevents legacy `await` symbols to stop working _without_ changing the function from synchronous to asynchronous. – Petruza Sep 01 '21 at 13:17
  • 1
    There isn't really a reason why async would be required. You can still use a synchronous function and implement your own _await_ by looping over until the Promise is resolved, effectively making the parent function and the called functions all synchronous. I've read on other answers that the reason is to avoid blocking the main event loop, but everything you do synchronously does that so, what's the gain? – Petruza Sep 01 '21 at 13:21
  • @Petruza: You have to allow other code to run, though, or else the promise would never be resolved. – Stephen Cleary Sep 01 '21 at 20:58
  • What I don't understand is all this crazyness of having promises if all we end up doing is calling all asynchronous code with await, then what's the point? – Petruza Sep 23 '21 at 21:36
  • 1
    @Petruza: The goal is more maintainable code. In terms of maintainability, `await` > `then` > callbacks. – Stephen Cleary Sep 24 '21 at 12:22
  • I see, but if APIs offer asynchronous code that we force into being called synchronously with await, what's the point besides maintainable code? – Petruza Sep 24 '21 at 13:41
  • I mean, is `const result = await fetch(...); console.log(result)` better or more maintainable than `const result = fetchSync(...); console.log(result)`? Imagine longer await chains. – Petruza Sep 24 '21 at 13:43
  • 1
    @Petruza: Asynchronous is better than synchronous in most environments because it frees up the main thread. In the browser, synchronous code freezes the UI. On the server, synchronous code prevents other requests from being handled. If you are writing a command-line process/script, then synchronous code is OK, and async-or-sync becomes a matter of personal preference. Even in that environment, though, async allows concurrency if you need it. – Stephen Cleary Sep 24 '21 at 13:48
  • So if I call `await func();` that is in fact running asynchronously, only that the calling scope is _asleep_ until the Promise is resolved? – Petruza Sep 24 '21 at 15:12
  • @Petruza: I'm not sure what you mean by "asleep". `await` causes the function to return an unresolved Promise. Eventually it will return to the JS runtime, which executes a main loop of sorts that handles events. – Stephen Cleary Sep 25 '21 at 14:27
  • By asleep I mean that the scope calling `await func()` blocks execution until the Promise returned by `func` is either resolved or rejected. – Petruza Sep 27 '21 at 13:26
  • @Petruza: If by "scope ... blocks" you mean that local variables are kept in memory, then yes. If you mean that there's an execution stack and thread being blocked, then no. – Stephen Cleary Sep 27 '21 at 15:08
  • It seems I'm not communicating well enough, what I mean is this: https://jsfiddle.net/eLxjuk07/1 Let's imagine that timeout is fetch or any other code that takes a non trivial amount of time. – Petruza Sep 30 '21 at 14:38
  • @Petruza: `await` does not block, but it does pause its method. During the `await`, other things can happen (e.g., other timeout callbacks). But the method will not continue executing until after the `await`. – Stephen Cleary Oct 01 '21 at 13:57
7

Copied from https://stackoverflow.com/a/41744179/1483977 by @phaux:

These answers all give valid arguments for why the async keyword is a good thing, but none of them actually mentions the real reason why it had to be added to the spec.

The reason is that this was a valid JS pre-ES7

function await(x) {
  return 'awaiting ' + x
}

function foo() {
  return(await(42))
}

According to your logic, would foo() return Promise{42} or "awaiting 42"? (returning a Promise would break backward compatibility)

So the answer is: await is a regular identifier and it's only treated as a keyword inside async functions, so they have to be marked in some way.

Fun fact: the original spec proposed more lightweight function^ foo() {} for async syntax.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
Michael Cole
  • 15,473
  • 7
  • 79
  • 96
  • But doesn't the use of `await` require a space? I mean, is something like `var a = await foo()` could valid code if await were just a variable? – user4052054 Jan 12 '19 at 22:02
  • Why would foo() return 'awaiting 42'? It is obvious by construction that it would. Not for the actual await I would guess that return await(somePromise) would wait for some Promise to be resolved before returning the resolution. Or at least that seems like a useful shorthand. But actually such a delayed return IS basically a Promise. OK, got it. – Samantha Atkins Jan 16 '19 at 22:32
  • 1
    @user4052054 Yes, but `await (expressionThatEvaluatesToAPromise)` is a valid syntactic construction to await a promise, and `await (5)` is a valid way to pass the argument `5` to the user-defined function `await`. – philraj Jun 12 '19 at 14:36
1

Because using await inside middleware function means the middleware function can't return a result immediately (it must wait until await is settled) and middleware function callers must wait until the promise (returned from middleware function) is settled.

Mihail H.
  • 1,555
  • 16
  • 18