Rebuild a small typed loader from the ideas above. A transport returns Promise<unknown>. The loader waits for it, handles transport failure with a stable message, validates a numeric payload, and returns Result<number>.
A factory createLoader(transport) returns a zero-argument async function. This keeps dependency configuration separate from each call and makes a fixture easy to substitute in tests. The signature should preserve the difference between calling the factory and calling the returned loader.
Test a success with zero, a successful-but-invalid string payload and a rejected transport. Do not use any, assertions or a broad catch that labels every possible programming bug as a transport failure. The final project replaces this numeric decoder with the catalogue decoder from Day 4.
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 Loader = () => Promise<number>;
function createLoader(value: number): Loader {
return async () => value;
}
const load = createLoader(25);
console.log(await load());Enable JavaScript to edit and type-check this example.
Check your prediction
25 Acceptance cases before you code
Write a small test plan before implementing typed loader boundaries. 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 |
|---|---|
| Valid zero transport data | Produce success containing zero |
| Successful malformed data | Produce the domain failure |
| Transport rejection | Produce the transport failure without pretending data was validated |
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: Typed Loader: practise
Write createLoader(transport:()=>Promise<unknown>) returning ()=>Promise<Result<number>> with finite nonnegative integer validation. Print 0, Invalid count and Unavailable for the three scenarios described above.
// Keep transport invocation inside the narrow try block and validation afterward.Enable JavaScript to edit and type-check this example.
Hint 1: the idea
The factory returns a function; it does not fetch during configuration.
Hint 2: a first step
Define the returned function as async and return its result variants.
Show one solution
type Result<T> =
| {
ok: true;
value: T;
}
| {
ok: false;
message: string;
};
function createLoader(
transport: () => Promise<unknown>
): () => Promise<Result<number>> {
return async () => {
let value: unknown;
try {
value = await transport();
} catch {
return { ok: false, message: "Unavailable" };
}
if (
typeof value !== "number" ||
!Number.isFinite(value) ||
!Number.isInteger(value) ||
value < 0
) {
return { ok: false, message: "Invalid count" };
}
return { ok: true, value };
};
}
for (const transport of [
async () => 0,
async () => "0",
async () => {
throw "offline";
}
]) {
const load = createLoader(transport);
const result = await load();
console.log(result.ok ? result.value : result.message);
}Enable JavaScript to edit and type-check this example.
0
Invalid count
UnavailableExplain 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
Count calls to prove creating a loader does not invoke the transport and each load invokes it once. Add runtime assertions for every result branch. A static Promise<Result<number>> signature cannot guarantee that your decoder checks every domain rule.
Completion is saved in this browser only. It does not measure mastery.