Chaining Multiple Promises
The last post chained two .then() calls to parse JSON. This one chains a whole second network request onto the same flat pattern.
The last post already had a small chain: fetch into .then(response => response.json()) into .then(data => ...). Stretching that same pattern to a real second request, fetching a neighboring country, turns out to need exactly one new idea.
What a .then() Actually Returns
Every .then() returns a new Promise, whether or not its callback returns anything. But if the callback does return a value, that value becomes the fulfilled value of the next .then() in the chain.
Promise.resolve(value) is a shortcut for a Promise that's already fulfilled with that value, useful right here for testing the chain without a real network call.
Hit Run. The second .then() logs 23, not 'anything', because that's exactly what the first one returned. And if what gets returned is itself a Promise (a new fetch call, say), the chain waits for that Promise to settle before handing its value down to the next step.
Chaining a Real Second Request
That's the whole trick needed to fetch a neighboring country: return the second fetch call from inside the first one's .then(), instead of just calling it and moving on.
Hit Run and both cards appear, Portugal then Spain, four .then() calls deep and still perfectly flat. That's the actual payoff: this chain could grow to ten steps, the neighbor of the neighbor of the neighbor, and every one of them would sit at this same indentation level.
Notice the last .then() takes data directly, no [data] destructuring this time. That's the same array-vs-object gotcha from a few posts back: searching by name returns an array, searching by an exact code returns the object itself.
The Guard Clause Has a Gap
That if (!neighborCode) return; line has a real problem, worth naming rather than hiding. An island with no borders hits that line and returns undefined instead of a Promise. The next .then() still expects a response to call .json() on, and undefined isn't one.
That's not a mistake to fix here. Handling it properly needs error handling, .catch() specifically, which is exactly what's next.
The Nesting Mistake This Chain Avoids
There's a very common way to get this wrong: chaining the second .then() directly inside the first one's callback instead of returning it.
// Don't: nesting recreates callback hell
Promise.resolve('data').then((value) => {
Promise.resolve(value.toUpperCase()).then((upper) => {
console.log(upper);
});
});// Do: return the inner Promise and keep the chain flat
Promise.resolve('data')
.then((value) => Promise.resolve(value.toUpperCase()))
.then((upper) => console.log(upper));Both versions run and produce the same output, which is exactly why this mistake is easy to miss. The first one still works, it just quietly rebuilds the exact staircase Promises exist to escape, one callback nested inside another.
ExpandChaining stays flat: each .then() sits at the same indentation level, while nesting one .then() inside another climbs back into the same staircase Promises are supposed to escape.
The Essentials
- A
.then()callback's return value becomes the next.then()'s value. - Returning a Promise from a
.then()makes the chain wait for it before continuing. - A chain can grow to as many steps as needed, ten or more, without ever nesting.
- Chaining a
.then()inside another.then()'s callback recreates callback hell. Return the inner Promise instead. - A guard clause that returns without a value passes
undefineddown the chain, breaking the next step. Fixed with error handling, next post.
Keep reading