A type can represent a request identity, but only a runtime comparison can decide whether a completion belongs to the current request. A reducer is a function taking the current state and an action, then returning the next state. It does not start requests or change its input object. Implement one for idle, loading and ready states. Start creates loading with an id; finish is accepted only while loading with that same id.
Request ids are generated by the caller and must be unique within its lifecycle. Starting request 2 after request 1 makes a later finish for 1 stale. Ignore it by returning the exact unchanged state object. This makes a no-op observable and avoids unnecessary updates.
The TypeScript state union prevents reading an id from idle without narrowing. It does not prove ids are unique or increasing. Keep those assumptions documented and test both matching and stale transitions.
Predict, run, and explain
Write the output and trace the important state changes before checking it. Then run the program. Use Check types first; execution is allowed only when the real compiler accepts the source.
type State =
| { tag: "idle" }
| {
tag: "loading";
request: number;
};
const state: State = { tag: "loading", request: 2 };
console.log(state.request === 1);
console.log(state.request === 2);Enable JavaScript to edit and type-check this example.
Check your prediction
false
true Acceptance cases before you code
Write a small test plan before implementing request-state ownership. These cases extend the brief; they are not a second worked solution. Predict the expected behavior independently, then test through the public function. A good ordinary example and a good boundary example should catch different possible mistakes.
| Case to try | What your result should demonstrate |
|---|---|
| Finish old request while newer is loading | Return the exact newer state unchanged |
| Finish matching current request | Construct ready with its title |
| Finish while idle or already ready | Follow the documented no-op transition rule |
After the core task passes, deliberately break one rule and identify the case that notices it. Restore the implementation and explain the failure in terms of the contract, not the number of assertions. If a case requires a concept you have not recalled, revisit the relevant lesson before opening the solution.
Your turn
Checkpoint: Request State: practise
Write reduce for start(request) and finish(request,title). Start 1, then start 2. Finishing 1 must return the same loading object; finishing 2 must produce ready with title Types. Print true and Types.
// Define state and action unions, narrow before comparing ids, and keep stale events as no-ops.Enable JavaScript to edit and type-check this example.
Hint 1: the idea
A matching identity is a runtime requirement beyond the action shape.
Hint 2: a first step
In finish, check state.tag and state.request before constructing ready.
Show one solution
type State =
| { tag: "idle" }
| {
tag: "loading";
request: number;
}
| {
tag: "ready";
request: number;
title: string;
};
type Action =
| {
type: "start";
request: number;
}
| {
type: "finish";
request: number;
title: string;
};
function reduce(state: State, action: Action): State {
if (action.type === "start") {
return { tag: "loading", request: action.request };
}
if (state.tag !== "loading" || state.request !== action.request) {
return state;
}
return { tag: "ready", request: action.request, title: action.title };
}
let state = reduce({ tag: "idle" }, { type: "start", request: 1 });
state = reduce(state, { type: "start", request: 2 });
console.log(
reduce(state, { type: "finish", request: 1, title: "Old" }) === state
);
state = reduce(state, { type: "finish", request: 2, title: "Types" });
console.log(state.tag === "ready" ? state.title : "Unexpected state");Enable JavaScript to edit and type-check this example.
true
TypesExplain why the implementation meets the stated contract. Which part is enforced by the language or checker, and which part needs behavioral evidence?
Change it and test it
Test finish while idle and duplicate finish after ready. Add an error completion with the same identity rule and test stale failure as well as stale success. Explain why declaring request:number does not make the stale guard unnecessary.
Completion is saved in this browser only. It does not measure mastery.