Skip to main content

Command Palette

Search for a command to run...

For that promise tricky question

Updated
4 min read
D

I write here to learn, feel free to correct.

Good catch. The difference is:

  • in foo, the await is directly inside foo

  • in two, the await is waiting on another async function’s promise

So in one case, foo queues its own continuation immediately. In the other, two cannot queue its continuation yet, because it is waiting for one() to finish first.

Case 1: direct await

async function foo() {
  console.log("B");
  await null;
  console.log("C");
}

When execution reaches:

await null;

JavaScript does this:

  1. converts null to Promise.resolve(null)

  2. pauses foo

  3. queues the rest of foo as a microtask

So yes, this part:

console.log("C");

gets queued right away.

Because foo is directly awaiting that value itself.


Case 2: two awaiting one()

async function two() {
  console.log("two-1");
  await one();
  console.log("two-2");
}

When execution reaches:

await one();

JavaScript first has to evaluate one().

That call returns a promise. But at that moment, that promise is still pending, because one() has not finished.

So two does this:

  1. call one()

  2. get a pending promise back

  3. pause two

  4. attach continuation logic to resume two when that promise resolves

But two-2 is not placed in the microtask queue immediately, because the promise from one() is not resolved yet.

It only gets queued later, when one() completes.


Compare them side by side

foo

async function foo() {
  console.log("B");
  await null;
  console.log("C");
}

At await null:

  • null becomes an already-resolved promise

  • so resume-foo can be queued immediately

Queue:

[ resume foo -> print C ]

two

async function two() {
  console.log("two-1");
  await one();
  console.log("two-2");
}

At await one():

  • first one() runs

  • one() returns a pending promise

  • two pauses

  • but resume-two cannot be queued yet

  • it will be queued only when one() resolves

So initially, no two-2 microtask is added yet.


Why await null queues immediately

Because await always works with a promise-like value.

So:

await null

is basically like:

await Promise.resolve(null)

And since that promise is already resolved, the continuation can be scheduled right away as a microtask.

Not run immediately inline — but scheduled immediately.


Why await one() does not queue two-2 immediately

Because one() does not return an already finished value. It returns a promise that becomes resolved only after one finishes its own async work.

And in your example:

async function one() {
  console.log("one-1");
  await Promise.resolve();
  console.log("one-2");
}

one() pauses at its own await, so the promise returned by one() stays pending until later.

So two() must wait.


Tiny timeline

foo

async function foo() {
  console.log("B");
  await null;
  console.log("C");
}

Timeline:

  1. print B

  2. hit await null

  3. queue C


two

async function two() {
  console.log("two-1");
  await one();
  console.log("two-2");
}

Timeline:

  1. print two-1

  2. call one()

  3. one() starts, then pauses

  4. two() pauses waiting for one()’s promise

  5. later one() resumes and finishes

  6. only then queue two-2


Super short rule

  • await queues the continuation when the awaited promise settles

  • if it is already resolved, that continuation can be queued immediately

  • if it is still pending, the continuation is queued later

So:

await null

→ queue continuation now

but

await one()

→ wait for one() to finish, then queue continuation

One-line mental model

await X does not mean “queue the next line now.” It means “pause here, and queue the next line when X is resolved.”

If you want, I can show this with a very small queue diagram for both snippets side by side.