completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

Events, microtasks & ordering

Learn why real clicks and script-dispatched events can run listeners and microtasks in different orders in JavaScript.

By the end, you can
  • 01
    Follow a clickPredict when listeners and microtasks run by asking one question: when is the stack empty?
  • 02
    Tell clicks apartExplain why a real click, click(), and dispatchEvent() can run microtasks at different moments.
  • 03
    Pick the right queueSay when promises, code after await, and timers run, and what isTrusted tells you, without guessing.

One click, two possible orders

A click can be started in two ways:

  • A person clicks the button with the mouse or the keyboard. The browser creates the click.
  • Your code calls button.click(). JavaScript that is already running creates the click.

Both clicks call the same listeners. A listener is a function you attach to an element so it runs when an event happens. But the two clicks can print the listeners' logs in a different order. This lesson shows why.

Definition

Event dispatch means the browser, or your code, calling the listeners for an event. A microtask is a small piece of work that waits in a line, called the microtask queue. A promise callback is one kind of microtask. Microtasks run at a checkpoint: a moment when no JavaScript is running.

Here is the puzzle. There are two listeners. Each listener prints one line and then adds one microtask to the queue. Then:

  • A real click prints: listener 1, microtask 1, listener 2, microtask 2.
  • A button.click() call prints: listener 1, listener 2, microtask 1, microtask 2.
Top: a real click prints listener 1, microtask 1, listener 2, microtask 2. Bottom: button.click() prints listener 1, listener 2, microtask 1, microtask 2.A real clicklistener 1microtask 1listener 2microtask 2button.click()listener 1listener 2microtask 1microtask 2
The same two listeners, in two orders. After a real click, each microtask runs right after its own listener. After button.click(), both microtasks wait until both listeners are done.

No promise is "racing" here. The answer depends on one question: is any JavaScript still running? The call stack, or just the stack, is the list of functions that are running right now. A microtask can only run when the stack is empty.

This question also helps when you debug. Two log lines can sit next to each other in the console and still come from different places. One may be normal code, one a microtask, and one a later task. Before you change any code, write down where each line came from.

Your app should not depend on these small details to work correctly. This lesson explains what the browser does, so that a failing test or a surprising log makes sense. Your own code can still choose a clear order on purpose.

A real click and a script click

Start with the script version. It is short, and you can run it. Two new tools appear in it:

  • queueMicrotask(fn) adds the function fn to the microtask queue. It does not run it yet.
  • button.click() clicks the button from code. It calls the button's click listeners right away.
A click started by code: button.click()Pop out in the code editor (opens in a new tab)JavaScript
const button = document.querySelector("#order-button"); button.addEventListener("click", () => {  console.log("listener 1");  queueMicrotask(() => console.log("microtask 1"));}); button.addEventListener("click", () => {  console.log("listener 2");  queueMicrotask(() => console.log("microtask 2"));}); button.click();

Lines 3 to 6 add listener 1. Lines 8 to 11 add listener 2. Each listener prints a line and queues one microtask. Line 13 calls button.click().

It prints listener 1, listener 2, microtask 1, microtask 2. Line 13 does not finish until both listeners have run. The stack is busy the whole time. Only when line 13 is done and the script ends can the microtasks run.

Now add the same two listeners and click the button yourself. The listeners are exactly the same. The only change is who starts the click: you, through the browser.

A real click: click the button yourselfPop out in the code editor (opens in a new tab)JavaScript
const button = document.querySelector("#order-button"); button.addEventListener("click", () => {  console.log("listener 1");  queueMicrotask(() => console.log("microtask 1"));}); button.addEventListener("click", () => {  console.log("listener 2");  queueMicrotask(() => console.log("microtask 2"));}); // Click the visible button with your mouse or keyboard.

In Chrome, a real click prints listener 1, microtask 1, listener 2, microtask 2. This lesson's test checks that with a real mouse click in Chrome. It checks the button.click() order too. Each order belongs to its own kind of click.

The order in which you add listeners still matters. Listener 1 was added first, so it runs first in both versions. The only difference is when the microtasks get their turn.

Pressing Enter or Space on a focused button is also a real click from a person. You can test it separately if a control has keyboard behavior. This lesson uses one button and the mouse, so the only change is who starts the click.

The table compares four things that look alike. Read each row as: what it is, who starts it, and what order you see.

Four ways listeners and microtasks can run
What you useWho starts it?What order do you see?
A real click from a personThe browser starts it. It runs each listener on its own, with an empty stack in between.Listener 1, microtask 1, listener 2, microtask 2, in Chrome.
button.click()Your running script starts it. The click is not trusted.Listener 1, listener 2, microtask 1, microtask 2.
dispatchEvent(event)Your running script starts it. It runs the listeners before it returns.The listeners run first. The next line waits until they are done.
A queued microtaskYour code queues it. It waits until the stack is empty.It runs before a later timer, never in the middle of running code.

Why a microtask can run between two listeners

A microtask never runs in the middle of other code. It waits until the stack is empty. This one rule explains both orders.

Left: while listener 1 runs, the stack is busy, so microtask 1 waits outside. Right: after listener 1 returns, the stack is empty, so microtask 1 runs.While listener 1 runslistener 1stackstack is busymicrotask 1waitsAfter listener 1 returnsmicrotask 1stackstack is empty, so it runs
A microtask never cuts into running code. It waits until the stack, the list of functions running right now, is empty.

The code below is a tiny model of that rule. It uses an array, messages, as a pretend microtask queue. messages.shift() takes out the first item in the array.

A tiny model: run both listeners, then the waiting messagesPop out in the code editor (opens in a new tab)JavaScript
const stack = ["script"];const messages = []; function listener(number) {  console.log("listener " + number);  messages.push("microtask " + number);} listener(1);listener(2);while (messages.length) console.log(messages.shift());

Line 5 prints one listener line. Line 6 adds its message to the queue. Lines 9 and 10 call both listeners. Only then does line 11 read the waiting messages, one by one. So the output is listener 1, listener 2, microtask 1, microtask 2. That is the button.click() order.

Real-life analogyPhone calls and messages

While you are on a call, new messages wait. You read them only after you hang up. A script click is one long call with both listeners, so you read the messages at the end. A real click is two short calls. Between them, you hang up and read your messages.

In real life: One long call with both people
In JavaScript: button.click() keeps the stack busy for both listeners
In real life: Two short calls, one after the other
In JavaScript: A real click runs each listener on its own
In real life: You check your messages after you hang up
In JavaScript: Microtasks run when the stack is empty
In real life: Messages wait while you are talking
In JavaScript: Microtasks never cut into running code

So queueing a microtask does not make it run right away. It runs at the next checkpoint. That is why copying the listener code alone cannot tell you the order. You also need to know who started the click.

Microtasks run in the order they were queued: first in, first out. Listener 1 queues microtask 1 before listener 2 queues microtask 2. So microtask 1 always runs before microtask 2. The puzzle is only about when the checkpoint happens.

A microtask can queue another microtask. All of them run before the browser moves on to the next task, such as a timer. So a long chain of microtasks can delay a timer and make the page slow to respond. Keep microtask work short.

Step through the puzzle

The two step-throughs below slow the puzzle down. Each one is a small model written in plain JavaScript. It uses the lesson's real order functions. It is not the browser's own debugger.

Step through a real click: a checkpoint after each listener
Step 0 of 8Ready
Your turn: follow the blue line

Step through a small model of a real click, where each listener runs on its own. It is a model in plain JavaScript, not the browser's debugger.

Running in
  1. script
Next: line 1
 function listener(number) {  log.push("listener " + number);  queueMicrotask(() => log.push("microtask " + number));} async function runUserModel() {  listener(1);  await Promise.resolve();  listener(2);  await Promise.resolve();  console.log(log.join(", "));}runUserModel();
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

In the real-click model, listener 1 finishes. Then the checkpoint runs microtask 1. Only then does listener 2 start. The listener code is nothing special. What changes is the empty stack between the two listeners.

Step through button.click(): one checkpoint at the end
Step 0 of 7Ready
Your turn: follow the blue line

Step through a small model of button.click(). It shows why both microtasks wait until both listeners are done.

Running in
  1. script
Next: line 1
 function listener(number) {  log.push("listener " + number);  queueMicrotask(() => log.push("microtask " + number));} listener(1);listener(2);queueMicrotask(() => console.log(log.join(", ")));
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

In the script model, the call to button.click() stays on the stack while it calls listener 1 and listener 2. The checkpoint comes only after that call returns. So both microtasks wait.

Each step shows the log or the queue at that moment. Back and Reset move through the recorded steps. They do not change the browser itself.

Try saying the two orders out loud:

  • Real click: "listener 1 ends, checkpoint, listener 2 ends, checkpoint."
  • Script click: "the script calls listener 1, then listener 2, then ends, then checkpoint."
Playground: a real click or a script click
The script click codePop out in the code editor (opens in a new tab)JavaScript
const button = document.querySelector("#order-button"); button.addEventListener("click", () => {  console.log("listener 1");  queueMicrotask(() => console.log("microtask 1"));}); button.addEventListener("click", () => {  console.log("listener 2");  queueMicrotask(() => console.log("microtask 2"));}); button.click();
Printed orderreal click
listener 1
microtask 1
listener 2
microtask 2
Try it yourself
Who starts the click?

A real click: the stack is empty after each listener, so each microtask runs right after its own listener. Listener 1, microtask 1, listener 2, microtask 2.

The order on the right comes from the lesson's two order models. To see the browser's real order, click the button in the real-click example above.

In the playground, change one thing: who starts the click. Reset goes back to the real click. The output is short, so you can see exactly where each microtask lands.

dispatchEvent runs listeners right away

target.dispatchEvent(event) sends an event to a target from your code. It is synchronous: it runs all the matching listeners before it returns to the next line. The code below also uses two new tools:

  • new EventTarget() makes a plain object that can have listeners. It works like a button with no page around it.
  • new Event("order") makes an event named order.
dispatchEvent() runs its listener before it returnsPop out in the code editor (opens in a new tab)JavaScript
const target = new EventTarget();target.addEventListener("order", () => console.log("listener")); console.log("before dispatch");target.dispatchEvent(new Event("order"));console.log("after dispatch");

Line 2 adds a listener for order. Line 4 prints before dispatch. Line 5 calls the listener, which prints listener. Line 6 prints after dispatch. So the output is before dispatch, listener, after dispatch.

The console prints before dispatch on line 4, then listener on line 5, then after dispatch on line 6.before dispatchlistenerafter dispatchline 4line 5line 6
dispatchEvent() runs the listener before it returns. So listener prints between the two outer lines.

So dispatchEvent() does not "send the event later". It calls the listeners straight away, like a normal function call. A listener can still queue a microtask. That microtask waits until the stack is empty.

This makes dispatchEvent() useful in tests:

  1. Set up a target and its listener.
  2. Call dispatchEvent().
  3. Check the listener's effects on the very next line.

If the listener starts work that finishes later, wait for that promise or timer before you check the result. Then the test says clearly what it waits for.

await inside a listener

A listener can be an async function. An async function runs right away, line by line, until it reaches its first await. There it pauses. The rest of the function goes into the microtask queue. Other listeners can run before it continues.

Promise.resolve() makes a promise that is already finished. So await Promise.resolve() waits for nothing, but the rest of the function still has to wait for the next checkpoint.

await puts the rest of the listener in the queuePop out in the code editor (opens in a new tab)JavaScript
const target = new EventTarget(); target.addEventListener("order", async () => {  console.log("listener 1");  await Promise.resolve();  console.log("after await");});target.addEventListener("order", () => console.log("listener 2")); target.dispatchEvent(new Event("order"));console.log("after dispatch");

Line 10 dispatches the event. Line 4 prints listener 1. Line 5 reaches await, so the first listener pauses. The listener from line 8 runs and prints listener 2, in the same dispatch. Line 11 prints after dispatch. Now the stack is empty, so the microtask runs line 6 and prints after await.

So await does not make the whole listener run later. It splits the listener in two:

  • The part before await runs right away.
  • The part after await waits in the microtask queue.

You often see this split in a form handler. The first part runs right away and can turn off the submit button. The second part runs when the request is done and shows the result. If another listener needs that result, connect the two on purpose. Do not hope the second part runs before the other listener.

A timer and a promise

Promises and timers both run code later. But they wait in different queues:

  • A promise callback, such as the function you pass to .then(), is a microtask.
  • setTimeout(fn, 0) runs fn after a delay of 0 milliseconds. The browser runs it in a later task: a separate turn of the event loop, after all microtasks are done.
A promise callback runs before a zero-delay timerPop out in the code editor (opens in a new tab)JavaScript
const target = new EventTarget(); target.addEventListener("order", () => {  console.log("listener");  Promise.resolve().then(() => console.log("promise"));  setTimeout(() => console.log("timer"), 0);}); target.dispatchEvent(new Event("order"));console.log("after dispatch");

Line 9 dispatches the event. Line 4 prints listener. Line 5 queues the promise callback. Line 6 queues the timer. Line 10 prints after dispatch. Now the stack is empty, so the microtask prints promise. The timer runs last, in a later task, and prints timer.

The console prints listener and after dispatch right away, then promise as a microtask, then timer as a later task.listenerafter dispatchpromisetimerright awayright awaymicrotasklater task
Code on the stack runs first. Then the promise callback runs, because it is a microtask. The timer runs last, in a later task.

A delay of 0 means the timer may run soon. It does not let the timer jump ahead of code on the stack, or ahead of the microtasks that run after it.

"May run soon" is not a promise about the clock. The browser can delay a timer for many reasons:

  • Other tasks are waiting first.
  • Timers nested inside other timers get a small minimum delay.
  • A tab in the background runs timers less often.

The one fact you can rely on here is the order. A promise callback queued by this code runs before the later timer task.

Trusted events

Every event has an isTrusted property. It is true when the browser created the event from a real action by a person. A script can ask a button to click, but that click is never trusted.

A click started by code is not trustedPop out in the code editor (opens in a new tab)JavaScript
const button = document.querySelector("#order-button");button.addEventListener("click", (event) => console.log(event.isTrusted)); button.click();

Line 2 adds a listener that prints event.isTrusted. Line 4 clicks from code, so it prints false. If you click the button yourself, the browser creates the event, and it prints true.

isTrusted does not change the order of anything. It is not a security check you should build on either. It only tells you where the event came from: a person, or code. That matters when code must tell a real person's action apart from a test.

Most app code should work the same for both kinds of event. A test that calls click() should still check the checkout total or the chosen tab. Only check isTrusted when a feature really needs a real person's action, and test that feature in a real browser.

Ordering puzzles

Ordering questions get easier when you give every line one of three labels:

  • runs now, on the current stack;
  • a microtask, which waits for the stack to be empty;
  • a later task, such as a timer.

You do not need to learn a long list of functions by heart.

Put each piece of code in its queue
  • A listener called by dispatchEvent()
  • The line after target.dispatchEvent(event)
  • A Promise.resolve().then(...) callback
  • Code after await Promise.resolve()
  • A setTimeout(..., 0) callback
  • The next real click from a person
Try it yourself
0 of 6 correct

Sort each card by when it can run. Read the explanation after each one.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

A listener called by dispatchEvent(), and the line after it, run now. Promise callbacks, and code after an await of a finished promise, are microtasks. Timers, and the next real click from a person, are later tasks.

The same labels help you debug. Add short logs that name each label. Then compare the order you see with the order you expected.

Keep the log names plain, such as "sync start", "promise checkpoint", and "timer task". Avoid names like "fast" and "slow". Busy code can delay a microtask, and a timer can run quickly on a quiet page. The queue tells you more than a guess about speed.

Event order in real code

Real pages often have several listeners for the same event: one for analytics, one to check the form, one to update the page. Your code should work the same whether the click came from a person, a test, or a helper that calls click().

When order matters, write the order down in code:

  • Put the steps that depend on each other in one listener.
  • Or call named functions one after another.
  • Or await the work that must finish first.

Then a test click and a real click follow the same steps. Some rules of thumb:

  • Use one named function for work that must happen in order.
  • Use microtasks for short follow-up work, not as a hidden way to order steps.
  • Use timers only when you really need the work to happen in a later task.
  • Test features that need a real person's click on their own, apart from the normal order checks.

These lessons explain adding listeners, how events travel through the page, and events you create yourself: Events introduction, bubbling and capturing, and custom events.

When you report an order bug, write down three things before you suggest a fix: who started the event, the names of the listeners, and where each log runs (now, microtask, or later task). A short example that uses dispatchEvent() can show whether the bug is in your own steps or only appears with a real click.

Common misconceptions

  • "All clicks are the same." Who starts a click can change isTrusted and when microtasks run.
  • "A microtask runs as soon as I queue it." It waits until the stack is empty.
  • "await pauses every listener." It pauses only its own async function, at that await.
  • "A timer with 0 delay runs right away." It runs in a later task.
  • "dispatchEvent() acts like a person clicking." It calls the listeners right away from your code, and the event is not trusted.

The table below lists four ideas that are easy to mix up. Each row says what the idea is, and what it is not.

Four ideas that are easy to mix up
The ideaWhat is it?What is it not?
click()A click made by code. It runs the listeners right away, and isTrusted is false.It is not a real click by a person.
A promise callbackA microtask. It waits until the stack is empty.It is not a timer, and it does not run at the same time as other code.
awaitA pause in its own async function. The rest of that function waits in the microtask queue.It does not pause the other listeners of the same event.
dispatchEvent()A way to run an event's listeners right away from your code.It is not a way to make a real person's click.

Practice exercises

Exercise 1 · Warm-upPredict the dispatchEvent order

Read the small program. Type its three outputs in order, with commas between them.

Starter codePop out in the code editor (opens in a new tab)JavaScript
const target = new EventTarget();
target.addEventListener("order", () => console.log("listener"));
console.log("before");
target.dispatchEvent(new Event("order"));
console.log("after");

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · Warm-upPredict a script click

    Two listeners each queue one microtask. What order does a script call to button.click() print?

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeSplit an async listener

      The program prints start, then outside, then one more word. What word prints after outside?

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const target = new EventTarget();
      target.addEventListener("order", async () => {
        console.log("start");
        await Promise.resolve();
        console.log("later");
      });
      target.dispatchEvent(new Event("order"));
      console.log("outside");

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeFind the last callback

        One listener queues a promise callback and a timer with 0 delay. Which word prints last?

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        const target = new EventTarget();
        target.addEventListener("order", () => {
          Promise.resolve().then(() => console.log("promise"));
          setTimeout(() => console.log("timer"), 0);
        });
        target.dispatchEvent(new Event("order"));
        console.log("sync");

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · PracticeCheck a click made by code

          Your test calls button.click(). What does the event's isTrusted property say?

          Answer, then press Check. Spacing and letter case don’t matter.

            Exercise 6 · ChallengeFix a checkout button

            A checkout button checks the form in one listener and sends analytics in another. A test uses click() and sees a different order than a real click. What should the code do instead?

            Answer, then press Check. Spacing and letter case don’t matter.

              Check your understanding

              For each question, first ask who started the event. Then find the first moment when the stack is empty.

              Event order quiz · 7 questionsScore: first tries count
              1. Question 1 of 7Why can the same two listeners print in different orders?

                Choose an answer to see the explanation.

              2. Question 2 of 7What does this print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const target = new EventTarget();
                target.addEventListener("order", () => console.log("listener"));
                console.log("before");
                target.dispatchEvent(new Event("order"));
                console.log("after");

                Choose an answer to see the explanation.

              3. Question 3 of 7Where does code after await Promise.resolve() run?

                Choose an answer to see the explanation.

              4. Question 4 of 7What prints last?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                Promise.resolve().then(() => console.log("promise"));
                setTimeout(() => console.log("timer"), 0);
                console.log("sync");

                Choose an answer to see the explanation.

              5. Question 5 of 7What is event.isTrusted for button.click()?

                Choose an answer to see the explanation.

              6. Question 6 of 7Which one runs listeners before it returns?

                Choose an answer to see the explanation.

              7. Question 7 of 7What is the safest way to get a fixed order in app code?

                Choose an answer to see the explanation.

              Key takeaways

              • Who starts an event can change when microtasks run, and the value of isTrusted.
              • Microtasks run only when the stack is empty, at a checkpoint.
              • button.click() and dispatchEvent() call the listeners right away, before they return.
              • Promise callbacks and code after await are microtasks. Timers run in a later task.
              • When order matters in your app, write that order in your code. Do not depend on who started the click.

              Remember the one-liner.
              Event order depends on when the stack becomes empty, not only on the listener code you can see.

              Coming next: Timers in depth, which looks closely at zero delay, minimum delays, and what a timer really promises.

              CompleteFrontend Clear concepts. Working examples.