1. It allows you to reason linearly about code with IO mixed in, without blocking the entire event loop. This is incredibly useful. Computer programmers are very good at reasoning linearly. It is just much, much more comfortable [1] to think about code that uses async/await than the same code with promises.
2. I think the way that promises and async/await interop is very beautifully designed. The way it ends up working in practice is that you can write a function that "blocks"[2], but if you later decide you want to call it in parallel with something else, that's fine because async functions are just a thin wrapper around Promises. So you can just take that existing function and call `Promise.all()` on it. Similarly if you start with a function that returns a promise, and you decide you want to call it in a blocking way -- you just do `await thingThatReturnsPromise()`. This is an almost perfect example of primitives composing to be more than the sum of their parts.
3. (and this is worth the price of admission alone) error handling works properly and automatically. If you `await` an async function and it throws, you'll get a plain ol' JavaScript error thrown which you catch in the normal way. If I never debug another hung JavaScript app that dropped a rejected promise on the floor it will be too soon.
[1] please don't tell me that I just don't "get" asynchronous programming. I was writing js event handlers when you were in diapers (something something lawn something).
[2] `await` doesn't really block the event loop, it just appears to.
This sounds pretentious and detracts from an otherwise good comment, even if you didn’t mean it to.
Makes writing correct code easier.
With async/await the language takes care of lowering those control structures that straddle async operations, similarly a compiler lowering them into conditional branch instructions.
i.e.:
async function () { ... }
// or
async () => { ... }Note that that explanation was purposefully ignoring any implementation details about whether or not closures are actually being created/allocated and was purely focusing on whether the programmer had to type out/read closures as extra syntax.
Actually, functional programmers might point out that even without async/await, assignment in imperative languages is still just some syntactic sugar for actually using closures. That is, in functional languages, its almost never idiomatic to directly "assign" to a variable (that is, overwriting the variable's previous value); instead values are "bound" to variables in let statements (or whatever the language's idiomatic equivalent is)--and let statements are effectively syntactic sugar for function calls/closures.
If you're familiar with haskell: you can think of imperative code as haskell's do notation but with the Identity macro (aka no special mode of computation, just the results of each function flowing into the next with "assignment" converted into functions). Marking a function as async (and thus allowing you use to await inside it) is just switching that function to be under the Promise monad. (but be careful! JavaScript promises aren't strictly monadic in that they auto-resolve if nested... so you can't have "Promise Promise a")
(... Sorry, this ended up being a bit of a mess of a braindump because you specified "implicit" closures :) )
However if you have control flow in it (execute this async function 100 times, add the results, depending on the result call another async function, ...) things will get a lot more ugly with raw promises.