A new brief, not another reading queue
Build createStockReport(load, publish), returning an async refresh() function. load returns a promise for unknown data. Valid data is an array of records with a nonnegative integer quantity field. Publish the numeric total for the latest successful refresh, or the message “Invalid stock” for malformed data. A rejected latest load publishes its error message. Older successes and failures must be ignored. Keep input records unchanged.
Do not add a webpage before the core contract works. Write a pure total decoder, then the async ownership wrapper. Use injected functions and deferred promises to test the wrapper. You may consult explanations; record which hints you needed and retry a changed brief later.
For this first attempt, assume rejected operations use Error values. The required cases are a normal total, empty success, a malformed record, a rejected current load, and an older response finishing after a newer one. Cancellation and disposal are deliberate extensions, not hidden requirements.
Use the checkpoint block to make a first independent attempt and test one case. Finishing all variations may take longer; do not mark readiness merely because you copied the worked solution. You are checking whether you can choose and connect the tools yourself.
Predict and trace
Write the output before running. Follow the values through each call; if your prediction differs, identify the first step that surprised you.
const records = [{ quantity: 2 }, { quantity: 3 }];
let total = 0;
for (const record of records) {
total += record.quantity;
}
console.log(total);Check your prediction
5This only demonstrates the data shape. It does not validate unknown input or protect async ownership.
Build it yourself
On Your Own: Stock Report: practise
Implement the pure decoder and createStockReport. Print 5 for valid data and Invalid stock for a numeric string. Add the required schedule tests yourself before opening the solution. The worked answer includes both old success and old failure schedules.
// Start with decodeTotal(value), then wrap a load dependency.Hint 1: the idea
Keep validation separate from the current-generation decision. A throw in the decoder belongs to the same guarded catch as a rejected load.
Hint 2: a first step
Capture mine before await; validate after fulfillment; only publish when mine still matches.
Show one solution
async function main() {
function decodeTotal(value) {
if (!Array.isArray(value)) {
throw new Error("Invalid stock");
}
let total = 0;
for (const item of value) {
if (
item === null ||
typeof item !== "object" ||
Array.isArray(item) ||
!Number.isInteger(item.quantity) ||
item.quantity < 0
) {
throw new Error("Invalid stock");
}
total += item.quantity;
}
return total;
}
function createStockReport(load, publish) {
let generation = 0;
return async function refresh() {
const mine = ++generation;
try {
const total = decodeTotal(await load());
if (mine === generation) {
publish(total);
}
} catch (error) {
if (mine === generation) {
publish(error.message);
}
}
};
}
const values = [];
await createStockReport(
async () => [{ quantity: 2 }, { quantity: 3 }],
value => values.push(value)
)();
await createStockReport(
async () => [{ quantity: "2" }],
value => values.push(value)
)();
console.log(values[0]);
console.log(values[1]);
if (decodeTotal([]) !== 0) {
throw new Error("empty total");
}
const input = [{ quantity: 2 }];
decodeTotal(input);
if (input[0].quantity !== 2) {
throw new Error("input changed");
}
const failures = [];
await createStockReport(
async () => {
throw new Error("Offline");
},
value => failures.push(value)
)();
if (failures[0] !== "Offline") {
throw new Error("current rejection");
}
function deferred() {
let resolve;
let reject;
const promise = new Promise((yes, no) => {
resolve = yes;
reject = no;
});
return { promise, resolve, reject };
}
for (const olderFails of [false, true]) {
const old = deferred();
const recent = deferred();
const tasks = [old.promise, recent.promise];
const published = [];
const refresh = createStockReport(
() => tasks.shift(),
value => published.push(value)
);
const first = refresh();
const second = refresh();
recent.resolve([{ quantity: 7 }]);
await second;
if (olderFails) {
old.reject(new Error("Old failure"));
} else {
old.resolve([{ quantity: 1 }]);
}
await first;
if (published.length !== 1 || published[0] !== 7) {
throw new Error("stale publication");
}
}
console.log("Empty, rejection, and latest-result checks passed");
}
main();5
Invalid stock
Empty, rejection, and latest-result checks passedTry an empty array and null record. Use deferred loads in the same controller instance to prove latest wins; two separate factories would not test shared generation ownership. Then add cancellation as a new contract.
Test a changed requirement
Try an empty array and null record. Use deferred loads in the same controller instance to prove latest wins; two separate factories would not test shared generation ownership. Then add cancellation as a new contract.
Explain before moving on
Can you explain the wrapper without reading its source? Which test would fail if the catch forgot its token guard?
Completion is saved in this browser only. It does not measure mastery.