How Async Await Works

Async

async is basically a syntatic sugar of promise, consider the following code

async function foo(name) {
  console.log(name, "start");
  console.log(name, "middle");
  console.log(name, "end");
}

Which is essentially the same as

function foo(name) {
  return new Promise((resolve) => {
    console.log(name, "start");
    console.log(name, "middle");
    console.log(name, "end");
    resolve();
  });
}

even when you call

console.log("1")
foo("First");
console.log("2")

the javascript will execute these line by line anyways. So the call stack is as following:

[
	console.log("1"),
	console.log("first", "start"),
	console.log("first", "middle"),
	console.log("first", "end"),
	console.log("2"),
]

Await

await will pause the execution of the current async function until the promises is settled. It will then resolve the promise for us.

Consider the following:

async function foo(name) {
  console.log(name, "start");
  await console.log(name, "middle");
  console.log(name, "end");
}

Which in Promise way, equivalent to

function foo(name) {
	return new Promise(resolve => {
		console.log(name, "start")
		resolve(console.log(name, "middle"))
	}).then(() => console.log(name, "end"))
}

Pausing in here means that await will put the next function in the bottom of the stack and execute what it can first. As a result, it will put

console.log(name, "end") 

in the bottom of the stack

So if we have something like

console.log("1")
foo("First");
console.log("2")

The call stack will be

[
	console.log("1")
	console.log("First", "start")
	console.log("First", "middle")
	// put console.log("First", "end") bottom of the stack
	console.log("2")
	console.log("First", "end")
]

Note: it's still single thread here, it just delay the result to the bottom to execute the other thing first.

Rule and order

The two queues

QueueAlso calledWhat goes in itPriority
Microtask queue“VIP line”Promise callbacks (.then, await continuations), queueMicrotaskRuns first
Macrotask queue“normal line”setTimeout, setInterval, I/O callbacksRuns after

The rule

  1. Run the current synchronous code until it finishes.
  2. Run all microtasks, until that queue is empty (at that exact moment)
  3. Take one macrotask (like a timer) and run it.
  4. Go back to step 2 (microtasks), and repeat.
function loop() {
  Promise.resolve().then(loop);   // each microtask adds a new one
}
loop();

setTimeout(() => console.log("never prints"), 0);