Say When: Recursing Promises In JavaScript
Imagine there's an API that outputs ten items in a single request. You want to get all the items from this API, and you're gonna use JavaScript and promises. The problem is, you don't know if there's 2 pages of data or 200.
You can't do this well with a common .then()
chain, because you don't know how many requests you'll need:
makeRequest()
.then(makeRequest)
.then(makeRequest)
.then(makeRequest)
// Hmm...should I just make 1000 requests and toss out
// the hundreds of 404s that are requests beyond the bounds of the data?
Basically, you want to run a task over and over for an unclear number of times.
The boilerplate for doing this with promises is
const doSomethingABunchOfTimes = () => {
const doSomethingOneTime = () => {
return somethingThatReturnsAPromise()
.then(() => {
if (theWorkIsComplete) {
return someFinalValue;
}
return doSomethingOneTime();
});
}
return doSomethingOneTime();
}
doSomethingABunchOfTimes()
.then((someFinalValue) => {
// do something with the final value
});
doSomethingOneTime()
will run over and over again in series until a condition is met (theWorkIsComplete()
).
Here's a real-world example, where we get all the posts from my personal weblog's REST API:
const requestAsync = require('request-promise');
const getAllPagedResultsFromSomeAPI = () => {
const getASinglePageOfResultsFromSomeAPI = (page, allApiResults = []) => {
const url = `https://ericandrewlewis.com/wp-json/wp/v2/posts?page=${page}`;
return requestAsync(url)
.then((rawResponse) => {
const apiResults = JSON.parse(rawResponse);
allApiResults = [...allApiResults, ...apiResults];
return allApiResults;
})
.then((allApiResults) => {
page++;
return getASinglePageOfResultsFromSomeAPI(page, allApiResults);
})
.catch((error) => {
// The API returns a 400 if we're past the bounds of the data.
if (error.statusCode === 400) {
return allApiResults;
}
throw error;
})
}
return getASinglePageOfResultsFromSomeAPI(1, []);
}
getAllPagedResultsFromSomeAPI()
.then((allApiResults) => {
// do something productive with all my blog posts like cut them into those paper snowflake chains
});