coderain blog

How to Use Axios.get().then() in a For Loop and Run a Function After All Requests Complete

In modern web development, interacting with APIs is a common task. Often, you may need to fetch data from multiple endpoints—for example, retrieving user profiles for a list of user IDs or fetching product details for an array of product SKUs. A natural approach is to use a loop to iterate over the list and发起 (initiate) API requests with axios.get(). However, due to the asynchronous nature of JavaScript, simply using axios.get().then() inside a for loop can lead to unexpected behavior: your "after all requests" function might run before all requests complete, leaving you with incomplete or missing data.

This blog will demystify why this happens and provide a step-by-step solution using Promise.all() to ensure all requests finish before executing后续 (subsequent) code. We’ll cover common pitfalls, error handling, and best practices to help you master asynchronous API calls in loops.

2026-01

Table of Contents#

  1. Understanding the Problem: Why Axios in a For Loop Fails
  2. A Quick Refresher: Axios.get() and Promises
  3. The Challenge: Synchronous Loops vs. Asynchronous Requests
  4. The Solution: Using Promise.all()
  5. Handling Errors with Promise.allSettled()
  6. Complete Example: Fetch Data in a Loop and Process Results
  7. Common Pitfalls to Avoid
  8. Best Practices
  9. Conclusion
  10. 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:#

  1. Collect all Axios promises in an array inside the loop (do not use .then() here—we’ll handle results later).
  2. Pass the array to Promise.all() to wait for all requests to resolve.
  3. Process results in the .then() callback of Promise.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' and value: result for successful promises.
    • status: 'rejected' and reason: error for 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#

  1. 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 use Promise.all().

  2. Forgetting to Handle Errors: If you omit .catch() on Promise.all(), a single failed request will crash your app with an "unhandled rejection" error.

  3. Incorrect Loop Variable Scope: Using var instead of let in the loop can cause all promises to reference the final value of the loop variable (due to function scoping). Always use let for 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++) { ... }
  4. 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#

  1. 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
    }
  2. Validate Responses: Ensure API responses have the expected structure before processing (e.g., check for response.data existence).

  3. Set Timeouts: Prevent hanging requests with Axios timeouts:

    axios.get(url, { timeout: 5000 }); // Fail after 5 seconds
  4. Use Async/Await for Readability: While this blog focuses on .then(), async/await can 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.

References#