Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using array.map, promises, and setTimeout to update an array

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.

like image 210
thatvegandev Avatar asked Aug 26 '18 21:08

thatvegandev


2 Answers

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,

like image 128
jonathangersam Avatar answered Oct 21 '22 17:10

jonathangersam


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>
like image 37
Ori Drori Avatar answered Oct 21 '22 18:10

Ori Drori