You searched for sneakers.
Why are hoodie results showing up?
Two searches. One screen. Find out how an older reply takes over, then test a repair that keeps the latest search in charge.
Start here if you can follow a small function and know that a promise represents a result that may arrive later. Learn explains the extra terms. No account needed.
The latest request owns the screen
Requests do not necessarily finish in the order they start. If every completed request writes to the screen, an older response can overwrite a newer search. This is a race: the visible result depends on which operation finishes when.
Keep a generation counter with the feature. Each request captures its own ticket before awaiting work. Compare that ticket with the current generation immediately before each visible state update.
Three kinds of update need the same check
- Results: an older success must not replace the current result.
- Errors: an older failure must not cover a newer success.
- Loading: older cleanup must not hide the current search’s waiting message.
A finally block runs when try finishes, including when it returns. Guarding only the successful result leaves other writes unprotected.
Follow the ticket in real code
This fixed example lets the newer result finish first and ignores the older result. It demonstrates the data guard; Your turn checks errors and loading too.
Follow the code
let generation = 0;async function search(load) { const ticket = ++generation; const value = await load(); if (ticket !== generation) return; console.log(value);}let releaseOld;const old = new Promise(resolve => { releaseOld = resolve; });search(() => old);search(() => Promise.resolve("new"));releaseOld("old");Console output
0 linesNo output yet.
Press Play, or Step to go one line at a time.
More ways to follow this example
Call stack
No call running.
All captured values
No values captured at this pause.
Every step as text
This lists every pause, so it has more steps than the player.
- Step 1 · Before line 1
Next, work out 0 and store its value under generation. Any calls on the right must finish first.
searchasync function search(load) { … }
- Step 2 · Before line 8
Next, run this instruction:
letreleaseOld;. Follow its effect on the highlighted names and output.generation0
- Step 3 · Before line 9
Next, define the arrow function old. Defining it stores instructions for a later call; its body does not run yet.
releaseOldundefined
- Step 4 · Before line 9
Next, define the arrow function old. Defining it stores instructions for a later call; its body does not run yet.
resolvefunction () { … }
- Step 5 · Before line 10
Next, run this instruction:
search(() => old);. Follow its effect on the highlighted names and output.releaseOldfunction () { … }oldPromise (pending)
- Step 6 · Before line 3
Next, work out ++generation and store its value under ticket. Any calls on the right must finish first.
load() => old
- Step 7 · Before line 4
Start or read the awaited operation. This function can pause its continuation here while the caller continues.
ticket1generation1
- Step 8 · Before line 10
Next, run this instruction:
search(() => old);. Follow its effect on the highlighted names and output. - Step 9 · Before line 11
Next, run this instruction:
search(() => Promise.resolve("new"));. Follow its effect on the highlighted names and output. - Step 10 · Before line 3
Next, work out ++generation and store its value under ticket. Any calls on the right must finish first.
load() => Promise.resolve("new")
- Step 11 · Before line 4
Start or read the awaited operation. This function can pause its continuation here while the caller continues.
ticket2generation2
- Step 12 · Before line 11
Next, run this instruction:
search(() => Promise.resolve("new"));. Follow its effect on the highlighted names and output. - Step 13 · Before line 12
Next, run this instruction:
releaseOld("old");. Follow its effect on the highlighted names and output. - Step 14 · Before line 5
Check the condition against the current values. A loop checks again before another repetition; an if chooses its matching branch.
load() => Promise.resolve("new")ticket2value"new"oldPromise (fulfilled)
- Step 15 · Before line 6
Next, work out the values inside
console.log, including any function calls, then print the results. A called function may run before the message appears. - Step 16 · Output from line 6
The console just gained this message: new. Printing a value does not by itself change the stored value.
Console:
new - Step 17 · Return at line 7
This call returns Promise (fulfilled) to its caller. Its caller can now use that result.
return valuePromise (fulfilled)
- Step 18 · Before line 5
Check the condition against the current values. A loop checks again before another repetition; an if chooses its matching branch.
load() => oldticket1value"old"
- Step 19 · Return at line 5
This call returns Promise (fulfilled) to its caller. Its caller can now use that result.
return valuePromise (fulfilled)
- Step 20 · Finished
The recording has ended. Compare the final stored values with what actually appeared in the output.
About this example
Recorded from this exact example running in JavaScript. Steps pause before a statement, at a return, or after console output. Built-in implementation details are omitted.
The player replays a recorded run of this exact code. It doesn’t run code you change.
What this model leaves out
The mission uses two controlled requests and predetermined results. It does not model a whole network. Cancellation may save work, but cannot replace the ownership checks. A disposed feature must invalidate its tickets too. Unrelated features need independent counters.
Investigate, then choose a repair
Start with the original behavior. Release replies in either order and read each update. Changing the repair starts a fresh experiment.
Choose when the replies arrive
Try this order: finish sneakers, then hoodie. Finish the newer search first, then the older one. Which results stay on screen?
- Ticket 1 · Older searchhoodie
Waiting for a reply
- Ticket 2 · Current searchsneakers
Waiting for a reply
Search: sneakers
Results: No results yet
Loading: Searching…
Error: None
Follow each update
- Start search 1: hoodie.
- Start search 2: sneakers. Ticket 2 now owns the screen.
Test the repair beyond this case
Check successful replies, rejected requests, and loading cleanup. A correct final result can hide an earlier bad update.
Read the selected repair as code
The controls above use the same ownership rules as this teaching example. load and the screen functions stand for the mocked request and its UI updates.
let generation = 0;
async function search(query) {
const ticket = ++generation;
setLoading(true);
try {
const data = await load(query);
showResults(data);
} catch (error) {
showError(error);
} finally {
setLoading(false);
}
}Would you trust these answers?
These are authored sample AI-style answers, not a live chatbot. Some are correct. Use the experiment to decide, and explain your evidence.
Sample answer 1
Aborting the old request guarantees that none of its code can update the screen.
Sample answer 2
Only the latest request should update results, errors, and loading state.
Sample answer 3
A return inside try stops the finally block from running.
Try a different situation
A map requests a route to the museum, then to the park. The museum request settles late. Explain how you would protect the route, error message, and loading indicator, including a late rejection.
Your explanation stays on this page. It is not submitted or automatically graded.
Review your reasoning
The park request owns the latest destination. Each request captures its own ticket, and every visible write checks that ticket against the current one. Guard data, error, and finally cleanup. Cancellation can reduce work, but does not replace these checks.
Make a challenge for a friend
Choose a situation and a pair of searches. The link opens a fresh experiment without your repair, answers, or activity.
Changing a situation starts a fresh experiment. Your written explanation stays in the Check stage.
