A Promise Chain for Any Number of Steps
Why a hand-written promise chain only works for a fixed number of steps, and how map and reduce turn a list of any length into one chain, automatically.
Here's the actual idea this post teaches, in one sentence: .map() turns a list of inputs into a list of promises, and .reduce() folds that list into one chain, which means the chain works for a list of any length without writing a single extra .then() by hand.
The Limit of Writing It by Hand
The chain from the last two posts has exactly six .then() calls, written out by hand, because there were exactly three stats. That only works because you already knew the number was three.
Real lists don't always come with a known size ahead of time. Sometimes there are three items. Sometimes there are thirty. Copying and pasting .then() calls to match isn't a plan, it's a bug waiting for the day someone changes the list.
exercise.js in the code-practice repo. The solution is solution.js in the same folder.Step One: Turn the List Into Promises
Start with a plain array and getStat, the same lifted function from before:
const stats = ['inventory', 'shipping', 'payments', 'tax', 'insurance'];
const promises = stats.map(getStat);.map() calls getStat once for every item in the list, and each call kicks off its own request immediately. This one line both transforms the list and starts every request in parallel, exactly like creating each promise by hand did, just without writing one line per item.
Step Two: Fold the List Into One Chain
A list of five separate promises still isn't a chain. .reduce() is what turns a list into one combined thing, and a promise chain is exactly that: one combined thing built out of several smaller ones.
function combine(chain, pr) {
return chain.then(() => pr).then(output);
}
stats
.map(getStat)
.reduce(combine, Promise.resolve());reduce needs a starting point before it has anything to combine. Promise.resolve() is an already-finished promise, one with nothing to wait for, which makes it the right starting link for a chain that hasn't done anything yet.
Unrolled by hand, for this specific list, that's exactly the same shape as the chain from the last post:
Promise.resolve()
.then(() => inventoryPromise).then(output)
.then(() => shippingPromise).then(output)
.then(() => paymentsPromise).then(output)
// ...and so on, for however many items the list actually hasThe difference is nobody had to write out that unrolled version. reduce builds it, one link at a time, no matter how long stats turns out to be.
Finishing the Chain
reduce hands back one final promise, the whole chain, combined. Attach one more .then() to know when everything's done:
stats
.map(getStat)
.reduce(combine, Promise.resolve())
.then(() => output('All stats loaded'));Add a sixth item to stats, or drop it down to one. Nothing else in this code has to change. The chain always matches the list, because it was never actually hardcoded to three anything.
The Instinct Worth Keeping
The real skill here isn't memorizing this exact map/reduce combination. It's noticing the pattern that leads to it: whenever you catch yourself writing the same shape of code over and over, once per item, that's usually a sign the items belong in a list, with a tool doing the repeating for you instead of your fingers.
Keep reading