5

I am looking to iterate through an array of users (with only the id property set), call an endpoint every two seconds with each id, and store the associated user's name from the response into an updated array.

e.g. update [{ id: 1 }] to [{ id: 1, name: "Leanne Graham" }]

Here is my code:

const axios = require('axios');

const users = [{ id: 1 }, { id: 2 }, { id: 3 }];

function addNameToUser(user) {
  return new Promise((resolve) => {
    axios.get(`https://jsonplaceholder.typicode.com/users/${user.id}`)
      .then(response => {
        user.name = response.data.name
        resolve(user);
      });
  })
}

const requests = users.map((user, index) => {
  setTimeout(() => {
    return addNameToUser(user);
  }, index * 2000);
});

Promise.all(requests).then((updatedArr) => {
  console.log(updatedArr);
});

Everything works great without the setTimeout, but it's important that I only send a request every two seconds. So for three users, I would like to see the result from my Promise.all log after six seconds or so.

Worth noting: This is not the actual problem I am working on, but the easiest example I could come up with to help highlight the issue.

jrnxf
  • 744
  • 8
  • 22
  • 1
    https://stackoverflow.com/questions/23803743/what-is-the-explicit-promise-construction-antipattern-and-how-do-i-avoid-it – Benjamin Gruenbaum Aug 26 '18 at 21:59
  • Your mean is requests user1 and wait 2 seconds, requests user1 and wait 2 seconds, ... Or request user1 ended and wait 2 seconds, ... – hong4rc Aug 27 '18 at 11:46

2 Answers2

7

As I understand, the core of your question is how to space your processing by 2 seconds apart, right?

const users = [{ id: 1 }, { id: 2 }, { id: 3 }];

/* throttledProcess is a function that does your processing spaced by the given interval millisecond */
const throttledProcess = (items, interval) => {  
  if (items.length == 0) { // stop when there's no more items to process
    console.log('ALL DONE')
    return
  }  
  console.log('PROCESSING', items[0], Date()) // this is where your http call/update/etc takes place
  setTimeout(() => throttledProcess(items.slice(1), interval), // wrap in an arrow function to defer evaluation
    interval)
}

throttledProcess(users, 2000) // run process. shows output every 2 seconds

Run this code and every 2 secs, it will log out which user is being processed.

Hope this helps. Cheers,

jonathangersam
  • 1,137
  • 7
  • 14
6

You need to return a Promise from the map's callback. Since this promise will be resolved by the setTimeout(), we'll use the Promise constructor.

The resolved timeout promise should return the Axios promise, which will return the result when resolved.

Note: since Axios returns a Promise, we don't need to wrap it with another Promise constructor. See What is the explicit promise construction antipattern and how do I avoid it? question and answers.

const users = [{ id: 1 }, { id: 2 }, { id: 3 }];

const addNameToUser = (user) => 
  axios.get(`https://jsonplaceholder.typicode.com/users/${user.id}`)
  .then(({ data }) => ({
    ...user,
    name: data.name
  }));

const requests = users.map((user, index) =>
  new Promise(resolve => 
    setTimeout(() => resolve(addNameToUser(user)), index * 2000)
  ));

Promise.all(requests).then((updatedArr) => {
  console.log(updatedArr);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>
Ori Drori
  • 183,571
  • 29
  • 224
  • 209