
Scenario
This will help you if in order for you to perform a task you need to gather a bunch of information in a specific order. Another scenario is that you need to automate a bunch of requests in a particular sequence. I’m going to cover the three ways to do this in a recursive function.
And yes I know there might be a more efficient way to do this GraphQL but that might not solve making multiple queries to multiple APIs.
API
For this tutorial, we’re going to use https://jsonplaceholder.typicode.com as our means to retrieve data.

The scenario that we’re going to try and solve for is to make 3 requests and retrieve todo 1
, 3
, and 7
, in sequential order.
This is mainly for illustrative purposes, but in a more real-life scenario we might fetching data from our backend to get an address, then sending that request to google maps to get the parsed data, and then making another request to then display that map data.
Callbacks Recursion
Callbacks are the original way to sequentially perform requests. For this one, I’m going to avoid functions that return promises like Fetch
and stick with the original XMLHttpRequest
.
Defining Our XHR Http Request
const xhr = new XMLHttpRequest();xhr.addEventListener('load', (data) => {
console.log(data.currentTarget.response); // json data
console.log(data.currentTarget.status); // status code
});xhr.addEventListener('error', (error) => {
// default error handler, ex URL doesn't exist
// Not to be confused with 404 which is still a success
console.log('Something went wrong.');
});xhr.open('get', 'https://jsonplaceholder.typicode.com/todos/1');xhr.send();
Wrapping Request In Recursive Function
// Recursive Function
const reqs = (requests = [], store = [], failback) => {
// Check if there are still requests to make
if (requests instanceof Array && requests.length > 0) {
const xhr = new XMLHttpRequest();
// Success handling
xhr.addEventListener('load', (data) => {
const status = data.currentTarget.status;
const response = data.currentTarget.response;
if (status === 200) {;
// add to store
store.push(JSON.parse(response)); // remove first request from array of requests
requests.shift(); // move on to next request
return reqs(requests, store, failback);
} else {
if (failback) {
failback(`Request Error: ${status}`);
}
}
});
// Failure handling
xhr.addEventListener('error', (error) => {
if (failback) {
failback('Something went wrong.');
}
}); xhr.open(requests[0].method, requests[0].url);
xhr.send();
} else {
return store;
}
};// Init
const results = reqs([{method: 'get', url: 'https://jsonplaceholder.typicode.com/todos/1'}], [], (error) => console.log(error));// Output Results
console.log(results);
This should work right? Well, if we run it, output is:
undefined
Why is this? It’s because our http request hasn’t finished and we already output the result before things are finished.
Final Recursive Callback HTTP Request Code
We need to then change things to make it so that we pass a callback
function to let our recursive function know what to call when it’s done.
// Revised Recursive Function
const reqs = (requests = [], store = [], callback, failback) => {
// Check if there are still requests to make
if (requests instanceof Array && requests.length > 0) {
const xhr = new XMLHttpRequest();
// Success handling
xhr.addEventListener('load', (data) => {
const status = data.currentTarget.status;
const response = data.currentTarget.response;
if (status === 200) {;
// add to store
store.push(JSON.parse(response));
// remove first request from array of requests
requests.shift();
// move on to next request
return reqs(requests, store, callback, failback);
} else {
if (failback) {
failback(`Request Error: ${status}`);
}
}
});
// Failure handling
xhr.addEventListener('error', (error) => {
if (failback) {
failback('Something went wrong.');
}
});
xhr.open(requests[0].method, requests[0].url);
xhr.send();
} else {
if (callback) {
callback(store);
}
}
};
// Init
reqs([
{
method: 'get',
url: 'https://jsonplaceholder.typicode.com/todos/1'
},
{
method: 'get',
url: 'https://jsonplaceholder.typicode.com/todos/3'
},
{
method: 'get',
url: 'https://jsonplaceholder.typicode.com/todos/7'
}
],
[],
(data) => console.log(data), // Output results
(error) => console.log(error)
);
Now when we run it, we get the following output:
[
{"userId":1,"id":1,"title":"delectus aut autem","completed":false},{"userId":1,"id":3,"title":"fugiat veniam minus","completed":false},{"userId":1,"id":7,"title":"illo expedita consequatur quia in","completed":false}
]
Promises Recursion
With recursive promises we can take advantage of using fetch. One limit to note with fetch is that we cannot get the progress of the download, so if we wanted to progressing get the data, we wouldn’t be able to do and in that scenario I would just use xhr
and wrap it in a new Promise
.
Defining Our Fetch Http Request
const config = {
method: 'get'
};fetch('https://jsonplaceholder.typicode.com/todos/1', config)
.then(response => {
const json = response.json();
if (response.ok) {
return json;
}
return json.then(data => new Promise.reject(data));
})
.then(jsonData => {
console.log(jsonData);
})
.catch(error => console.log(error));
Final Recursive Promise HTTP Requests Code
// Promise Recursive Function
const reqsPromises = (requests = [], data = []) => new Promise((resolve, reject) => {
if (requests instanceof Array && requests.length > 0) {
const config = {
method: requests[0].method
};
return fetch(requests[0].url, config)
.then(response => {
const json = response.json();
if (response.ok) {
return json;
}
return json.then(data => reject(data));
})
.then(jsonData => resolve(reqsPromises(requests.slice(1), [ ...data, ...[jsonData] ])));
}
return resolve(data);
});// Init
reqsPromises([
{
url: 'https://jsonplaceholder.typicode.com/todos/1',
method: 'get'
},
{
url: 'https://jsonplaceholder.typicode.com/todos/3',
method: 'get'
},
{
url: 'https://jsonplaceholder.typicode.com/todos/7',
method: 'get'
}
],
[]
)
.then(data => console.log(data)) // Output results
.catch(error => console.log(error));
Async Await Recursion
The thing to remember with await
is that it still expects a Promise
and fortunately Fetch
is already a promise. The main thing to remember is that async
is basically turning a regular function into a Promise
.
Basically:
const myPromise = () => new Promise.resolve('hello');// = the same as const myAsync = async () => 'hello';
Defining Our Async Await Fetch Http Request
We wrap our Fetch
request in a try
and catch
which acts like a Promise
catch
and we await
the Fetch
because it’s already a promise and when we get the response we convert our non promise function into a promise one with async response => ...
.
try {
const config = {
method: 'get'
};
await fetch(
'https://jsonplaceholder.typicode.com/todos/1',
config
)
.then(async response => {
const json = response.json();
if (response.ok) {
return console.log(await json);
}
// not this object may be empty if no err msg
throw await json;
});
} catch (error) {
console.log('ERROR');
console.log(error);
}
You’ll also notice there is an additional await
on json
that is because response.json()
= json
is also a promise.
Final Recursive Async Await HTTP Requests Code
// Async Await Recursive Function
const reqsAsyncWait = async (requests = [], data = []) => {
if (requests instanceof Array && requests.length > 0) {
const config = {
method: requests[0].method
};
return await fetch(
requests[0].url,
config
)
.then(async response => {
const json = response.json();
if (response.ok) {
const jsonData = await json;
return await reqsAsyncWait(requests.slice(1), [ ...data, ...[jsonData] ]);
}
// not this object may be empty if no err msg
throw await json;
});
}
return data;
};// Init
try {
console.log(await reqsAsyncWait([
{
url: 'https://jsonplaceholder.typicode.com/todos/1',
method: 'get'
},
{
url: 'https://jsonplaceholder.typicode.com/todos/3',
method: 'get'
},
{
url: 'https://jsonplaceholder.typicode.com/todos/7',
method: 'get'
}
], []));
} catch (error) {
console.log(error);
}
Where To Go From Here
One of the main things that inspired this post was that I couldn’t chain promises for some of my Postman tests, so I had to create a recursive http callback function that performed multiple requests.
If you have control over the API, then I would recommend in most cases that the API data provides everything you need, but in some cases you can’t do that and you will need to perform requests to multiple APIs to all your needed data.
If you got value from this, and/or if you think this can be improved, please let me know in the comments.
Please share it on twitter 🐦 or other social media platforms. Thanks again for reading. 🙏
Follow me on twitter: @codingwithmanny and instagram at @codingwithmanny.
