Why does Async/Await work properly when the loop is inside the async function and not the other way around?

let i = 3;
while (i) {
  (async () => {
    await Promise.resolve();
    console.log(i);
    i--;
  })();
}

It may help if we rewrite the code without async/await to reveal what it is really doing. Under the hood, the code execution of the async function is deferred for later:

let callbacks = [];

let i = 0;
while (i > 0) {
  callbacks.push(() => {
    console.log(i);
    i--;
  });
}

callbacks.forEach(cb => {
  cb();
});

As you can see, none of the callbacks are executed until after the loop is completed. Since the loop never halts, eventually the vm will run out of space to store callbacks.