OK, I'm there: I have worked it out.
First of all, there is a difference between strict mode (see the MDN documentation). In strict mode, the two behave equivalently:
"use strict";
{
function f() {
return 1;
}
}
(async () => {
console.log(await f());
})();
"use strict";
{
async function f() {
return 1;
}
}
(async () => {
console.log(await f());
})();
Functions in blocks were actually entirely prohibited in strict mode until ES2015, but now behave in the expected way.
In sloppy mode, functions in blocks are technically not allowed by the spec, but were actually allowed by all implementations (MDN vaguely explains this). The function, confusingly, gets hoisted to the nearest function scope. This is not intuitive behaviour, which is why it is changed in strict mode.
To make things even more confusing, an asynchronous function in sloppy mode is scoped to the block, even though a normal function would be scoped to the function. I have not found anything that documents this behaviour.
{
console.log(g()); // works
console.log(f()); // works
async function f() {
return 1;
}
function g() {
return 2;
}
}
(async () => {
console.log(await g()); // works
console.log(await f()); //errors
})();
So the situation is confusing in sloppy mode, because of decisions made to ensure compatibility with old code. This is, of course, a constant refrain when developing in sloppy mode, which is why you should always use strict mode.
Thanks for a really interesting question!