If I understand correctly, you are trying to have something execute after all your fetching completes. My favorite way to do this is using Promises and the async await syntax. I rewrote your code trying to flatten things out, and it also will now wait until the loops are done before logging "loop end".
The first thing I do is wrap my entire action in an anonymous async function that is immediately called.
The reason I use async, is because it is needed to use await. Now instead of using .then chains, I can tell the code to wait for the fetch to complete before continuing. This works on not only the fetch which returns a promise, but the .json method that returns a promise as well.
The for of loop is similar to yours, but I use the const keyword to prevent changing the value of a global variable.
I assume there is always an id, and would have used id actions without the need of checking to see if there is an id.
Instead of going through each key and having an if statement which executes code when a certain key is found, I loop through the desired targets and skip executing the code if the key does not exist.
If you need to do something different for any of the keys, you can remove them from the loop and just repeat it, but it gets pretty wet. Anything that is repeated should be put into a function or loop to only be written once
If you have any questions please ask!
(async () => {
const response = await fetch(url);
const data = await response.json();
for (const item of data.list) {
//do something with id?
for (const key of ["name", "address", "city"]) {
if (!(key in item)) continue;
const keyRes = await fetch(`${url}/${item[key]}`);
const keyData = await keyRes.json();
//do something
console.log(`fetch item: ${key}`);
}
}
console.log("loop end");
})();
You mentioned that the fetches are taking too long happening in order, to give you an example of doing this without blocking in the loop, but blocking until all loop promises resolve, here is another example.
Here we write an async function processKey to handle the processing with async await syntax. Async function always return a promise, and the await keyword will wait for the promise to resolve. This way, we can call the function, but instead of waiting for it to resolve, we can store the promise in an array.
After we initiate all the promises without waiting for them to resolve, we can then use await Promise.all(promisesToWaitFor) and wait until all the promises in the array resolve before executing the last line of code.
It should be pointed out that we are saving time by no longer waiting for code in the loop to execute. Because of this, if one fetch takes less time than another, we will have actions in the loop happening "out of order". Depending on your use case, this may or may not be ideal.
If you need any clarification of the code, please ask!
async function processKey(key, url, item) {
const keyRes = await fetch(`${url}/${item[key]}`);
const keyData = await keyRes.json();
//do something
console.log(`fetch item: ${key}`);
}
const url = "Something";
(async () => {
const response = await fetch(url);
const data = await response.json();
const promisesToWaitFor = [];
for (const item of data.list) {
//do something with id?
for (const key of ["name", "address", "city"]) {
if (!(key in item)) continue;
const processPromise = processKey(key, url, item);
promisesToWaitFor.push(processPromise);
}
}
await Promise.all(promisesToWaitFor);
console.log("loop end");
})();
EDIT:
You mentioned that you wanted to keep things in order. I decided instead of preforming all the actions side by side, we could store all the .json promises into an array, then after they have all been initiated, step through that array waiting for each one to finish. This keeps the order of things while still getting some better speed.
One thing I wanted to clarify was combining async/await with promise chaining .then method.
I have the line
const jsonPromise = fetch(`${url}/${item[key]}`).then(res => res.json());
This will use the promise chain to trigger the action of fetching and converting to json in one line. I can store that Promise to convert to json in a variable, and then store that in an array. Since the action we wanted to preform with that data involved the key used to get the data, I pass both the promise and the key to an object in the array.
After we trigger all the fetching, then I have a separate loop which uses await to wait for the action to complete.
(async () => {
const response = await fetch(url);
const data = await response.json();
const fetchToWaitFor = [];
for (const item of data.list) {
//do something with id?
for (const key of ["name", "address", "city"]) {
if (!(key in item)) continue;
const jsonPromise = fetch(`${url}/${item[key]}`).then(res => res.json());
fetchToWaitFor.push({jsonPromise, key});
}
}
for (const waitObj of fetchToWaitFor) {
const {key, jsonPromise} = waitObj;
const data = await jsonPromise;
console.log(`fetch item: ${key}`);
}
console.log("loop end");
})();