Table of Contents#
- Understanding the Problem: Why Axios in a For Loop Fails
- A Quick Refresher: Axios.get() and Promises
- The Challenge: Synchronous Loops vs. Asynchronous Requests
- The Solution: Using Promise.all()
- Handling Errors with Promise.allSettled()
- Complete Example: Fetch Data in a Loop and Process Results
- Common Pitfalls to Avoid
- Best Practices
- Conclusion
- References
Understanding the Problem: Why Axios in a For Loop Fails#
Let’s start with a scenario: you want to fetch data for 3 users from an API and log all results once all requests complete. You might write code like this:
const axios = require('axios');
const results = [];
// Loop through user IDs 1-3
for (let i = 1; i <= 3; i++) {
axios.get(`https://jsonplaceholder.typicode.com/users/${i}`)
.then(response => {
results.push(response.data); // Push data to results array
console.log(`Fetched user ${i}`);
})
.catch(error => {
console.error(`Error fetching user ${i}:`, error);
});
}
// Code to run after all requests: Log results
console.log('All results:', results); Expected Output:
Fetched user 1
Fetched user 2
Fetched user 3
All results: [user1, user2, user3]
Actual Output:
All results: []
Fetched user 1
Fetched user 2
Fetched user 3
Why? Because axios.get() is asynchronous. The for loop runs synchronously, initiating all 3 requests immediately. However, the .then() callbacks (which push data to results) are queued in the JavaScript event loop and execute after the synchronous code (the console.log('All results:', results) line). Thus, results is still empty when logged.
A Quick Refresher: Axios.get() and Promises#
To fix this, we need to understand how axios.get() and promises work:
axios.get(url)returns a promise—an object representing the eventual completion (or failure) of an asynchronous operation.- A promise has three states: pending (initial), fulfilled (success), or rejected (error).
.then(callback): Registers a callback to run when the promise is fulfilled (e.g., when the API response arrives)..catch(callback): Registers a callback to handle errors if the promise is rejected.
Crucially, promises are asynchronous. The JavaScript engine prioritizes synchronous code (like loops) first, then processes asynchronous callbacks (.then()/.catch()) once the call stack is empty.
The Challenge: Synchronous Loops vs. Asynchronous Requests#
For loops are synchronous: they execute line-by-line, blocking until the loop completes. When you call axios.get() inside a loop, you’re initiating multiple asynchronous requests, but their .then() callbacks won’t run until after the loop finishes.
In the earlier example, the loop starts all 3 requests, then immediately runs console.log('All results:', results) before any .then() callbacks execute. Hence, results is empty.
The Solution: Using Promise.all()#
The key is to wait for all asynchronous requests to complete before running后续 code. This is where Promise.all() shines:
Promise.all(promisesArray)takes an array of promises and returns a single promise.- This promise resolves only when all promises in the array resolve, returning an array of their results (in the same order as the input promises).
- It rejects immediately if any promise in the array rejects (use
Promise.allSettled()if you need to wait for all, even failed, requests—more on this later).
How to Use It:#
- Collect all Axios promises in an array inside the loop (do not use
.then()here—we’ll handle results later). - Pass the array to
Promise.all()to wait for all requests to resolve. - Process results in the
.then()callback ofPromise.all().
Revisiting the earlier example with Promise.all():
const axios = require('axios');
// Step 1: Collect all Axios promises in an array
const promises = [];
for (let i = 1; i <= 3; i++) {
promises.push(axios.get(`https://jsonplaceholder.typicode.com/users/${i}`));
}
// Step 2: Wait for all promises to resolve
Promise.all(promises)
.then(responses => {
// Step 3: Process results (responses is an array of Axios response objects)
const results = responses.map(response => response.data);
console.log('All results:', results); // Now results are ready!
})
.catch(error => {
// Catches errors from ANY failed request
console.error('One or more requests failed:', error);
});Output:
All results: [
{ id: 1, name: 'Leanne Graham', ... },
{ id: 2, name: 'Ervin Howell', ... },
{ id: 3, name: 'Clementine Bauch', ... }
]
Now results contains data from all 3 requests because Promise.all() waits for all promises to resolve before executing its .then() callback.
Handling Errors with Promise.allSettled()#
Promise.all() rejects if any promise rejects, which may not be desired if you want to proceed even if some requests fail (e.g., logging errors but keeping successful results). Use Promise.allSettled() instead:
Promise.allSettled(promisesArray)returns a promise that resolves when all promises settle (either fulfill or reject).- The result is an array of objects with:
status: 'fulfilled'andvalue: resultfor successful promises.status: 'rejected'andreason: errorfor failed promises.
Example with Promise.allSettled():
const axios = require('axios');
const promises = [];
for (let i = 1; i <= 3; i++) {
// Intentionally fail for user 2 (invalid ID)
const url = i === 2 ? 'https://jsonplaceholder.typicode.com/invalid' : `https://jsonplaceholder.typicode.com/users/${i}`;
promises.push(axios.get(url));
}
Promise.allSettled(promises)
.then(results => {
const successfulData = [];
const errors = [];
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
successfulData.push(result.value.data);
} else {
errors.push(`Request ${index + 1} failed: ${result.reason.message}`);
}
});
console.log('Successful data:', successfulData);
console.log('Errors:', errors);
});Output:
Successful data: [
{ id: 1, name: 'Leanne Graham', ... },
{ id: 3, name: 'Clementine Bauch', ... }
]
Errors: [ 'Request 2 failed: Request failed with status code 404' ]
Complete Example: Fetch Data in a Loop and Process Results#
Let’s build a full example: fetch posts for 3 users, combine all posts, and display a summary.
Step 1: Define the API Endpoint and User IDs#
We’ll use JSONPlaceholder (a free fake API) to fetch posts by user ID.
Step 2: Fetch Data for Each User#
Collect Axios promises in a loop, then use Promise.all() to wait for all.
Step 3: Process Results#
Combine posts from all users and log a summary.
const axios = require('axios');
// User IDs to fetch posts for
const userIds = [1, 2, 3];
// Function to fetch posts for a user
const fetchUserPosts = (userId) => {
return axios.get(`https://jsonplaceholder.typicode.com/posts?userId=${userId}`);
};
// Step 1: Collect promises for all users
const postPromises = userIds.map(userId => fetchUserPosts(userId));
// Step 2: Wait for all requests to complete
Promise.all(postPromises)
.then(responses => {
// Step 3: Extract and combine posts from all responses
const allPosts = responses.flatMap(response => response.data); // Flatten array of arrays
console.log(`Fetched ${allPosts.length} posts total`);
console.log('Sample post:', allPosts[0]);
// Run function after all requests (e.g., render to DOM)
displayPostSummary(allPosts);
})
.catch(error => {
console.error('Failed to fetch posts:', error.message);
});
// Function to run after all requests complete
function displayPostSummary(posts) {
const summary = {
total: posts.length,
users: [...new Set(posts.map(post => post.userId))] // Unique user IDs
};
console.log('Post Summary:', summary);
}Output:
Fetched 30 posts total (10 per user)
Sample post: { userId: 1, id: 1, title: '...', body: '...' }
Post Summary: { total: 30, users: [1, 2, 3] }
Common Pitfalls to Avoid#
-
Using
.then()Inside the Loop: This leads to uncoordinated callbacks and race conditions (as shown in the initial problem example). Always collect promises first, then usePromise.all(). -
Forgetting to Handle Errors: If you omit
.catch()onPromise.all(), a single failed request will crash your app with an "unhandled rejection" error. -
Incorrect Loop Variable Scope: Using
varinstead ofletin the loop can cause all promises to reference the final value of the loop variable (due to function scoping). Always useletfor loop counters:// Bad: var i is function-scoped, all promises reference the same i for (var i = 1; i <= 3; i++) { ... } // Good: let i is block-scoped, each iteration has its own i for (let i = 1; i <= 3; i++) { ... } -
Overloading the API with Too Many Requests: Sending 100+ requests at once with
Promise.all()can trigger rate limits or 429 (Too Many Requests) errors.
Best Practices#
-
Throttle Concurrent Requests: For large numbers of requests (e.g., 100+), split them into smaller "chunks" and process sequentially to avoid overwhelming the API:
const chunkSize = 5; // Process 5 requests at a time const chunks = []; for (let i = 0; i < allUserIds.length; i += chunkSize) { chunks.push(allUserIds.slice(i, i + chunkSize)); } // Process chunks sequentially for (const chunk of chunks) { const promises = chunk.map(id => fetchUserPosts(id)); await Promise.all(promises); // Use async/await for readability } -
Validate Responses: Ensure API responses have the expected structure before processing (e.g., check for
response.dataexistence). -
Set Timeouts: Prevent hanging requests with Axios timeouts:
axios.get(url, { timeout: 5000 }); // Fail after 5 seconds -
Use Async/Await for Readability: While this blog focuses on
.then(),async/awaitcan make code cleaner (especially with loops). Example:async function fetchAllUsers() { const promises = []; for (let i = 1; i <= 3; i++) { promises.push(axios.get(`https://jsonplaceholder.typicode.com/users/${i}`)); } try { const responses = await Promise.all(promises); return responses.map(res => res.data); } catch (error) { console.error('Error:', error); } }
Conclusion#
Using axios.get().then() in a for loop requires careful handling of asynchronous operations. The key takeaways are:
- Collect Axios promises in an array instead of handling
.then()inside the loop. - Use
Promise.all()to wait for all requests to resolve and process results in one go. - Use
Promise.allSettled()if you need to handle partial failures. - Avoid common pitfalls like unhandled errors and scope issues with loop variables.
By following these patterns, you’ll ensure your code reliably waits for all API requests to complete before proceeding.