Skip to content

Promises inside of setInterval

I’ve got this routine function that runs every 60000ms on a setInterval function. Inside of this routine function, I loop over all the usernames on a parsed JSON(db) and check whether or not they’re available via a promise (checkUsername), which is a network request.

However, It’s pretty clear to me that this is a terrible approach because the promises may take more than 60 seconds to complete, and I’ve been getting ETIMEDOUT errors all over the place. But I just don’t understand promises and asynchrony enough to think of a solution.

What would be a better approach to this? Would async/await fit here?

function routine() { 
  db.users.forEach(userObj => {
   userObj.username.forEach(username => {
    checkUsername(username).then(hasUser => {
    if(!hasUser) {
      bot.sendMessage(userObj.chatId, `‼️ Username @${username} is now AVAILABLE ‼️`);
      removeName(username, userObj.chatId);

    }

    }).catch(err => { 
      console.log(err);

    })

    })
});
}
setInterval(routine, 120000);

Answer

I made a code snippet that you can run that makes use of Promise.all as well as async/await ES7 to make your code a bit easier to handle and understand. I’m also hitting a real API endpoint I found online just for the sake of a complete example.

I’ve also added a way to stop the timeout for good if you ever want that option.

// How often the timeout will run.
// Since the timeout is dependent on when all the requests finish, the timeout will run this many ms after all the requests finish.
var interval = 5000;

// This is what your db seems to resemble.
var db = {
  users: [{
      username: ['1']
    },
    {
      username: ['2']
    },
    {
      username: ['3']
    },
    {
      username: ['4']
    },
  ]
};

// This will hold the timeout function so you can cancel it at a later time.
var timeoutFn;

// Returns returns a single Promise that resolves when all of the promises it contains have resolved/rejected. It rejects with the first promise that rejects.
function routine() {
  console.log("-- Running routine function --");

  // Return an array of promises. Please see my comments at the bottom of this whole answer which questions db architecture with username being an array.
  // I'm also using map instead of forEach because map automatically returns and array.
  let promiseArray = db.users.map(userObj => {
    return Promise.all(userObj.username.map(username => {
      // This processUsername() function should do all the work related to the username. It helps to keep the routine function as clean as possible since there's already a lot happening in here.
      return processUsername(username);
    }));
  });

  // Returns an array of array of promises. This means that every single promise within each array (see above) has to resolve before the `then` runs. If any reject, `catch` will run instead.
  return Promise.all(promiseArray).then(() => {
    runRoutineAgain();
  }).catch((err) => {
    console.log('err:', err)
  });
}

// This will create a timeout and run routine after interval.
function runRoutineAgain() {
  timeoutFn = setTimeout(routine, interval);
}


// This async function returns a promise
async function processUsername(username) {
  // Make API call to get data
  console.log('Processing username for', username);

  // I'm using this free API endpoint online just for the sake of making a complete example. Obviously your API will be returning very different data.
  return await fetch(`https://jsonplaceholder.typicode.com/todos/${username}`)
    .then(response => response.json())
    .then((json) => {
      console.log(json);
      // This is where you can do your processing.
      // if(!hasUser) {
      //    bot.sendMessage(userObj.chatId, `‼️ Username @${username} is now AVAILABLE ‼️`);
      //    removeName(username, userObj.chatId);
      //  }
    });
}


function stopTimeout() {
  clearTimeout(timeoutFn);
}

routine();

Basically, I’m using Promise.all to capture and wait on the result of individual promises which is very useful since you have many users you need to get data for.

Feel free to open up Web Console to better see the output data.

I’m also using async/await ES7 syntax just to demonstrate other (easier, some might say) ways to write Promises. I understand Promises can be daunting so here are some links that really hit the nail on the head when trying to learn them.

  1. https://javascript.info/promise-basics – This covers Promises
  2. https://javascript.info/async-await – This covers async/await

Also is there a reason why you are looping over each username for a user in your original code?

db.users.forEach(userObj => {
   userObj.username.forEach(username => {…

If there’s only 1 username for a userObj, that second loop adds unnecessary complexity. But if your db has multiple usernames for a single userObj, then that’s completely fine!