setTimeout inside a loop, stops script from working

Alright. setInterval() is not a solution for me. Since I need to call the script every 15 minutes it should go through array and when it call all properties from array it suppose to stop. In setInterval() after calling the all properties in array in starts it again, this is doesn't what I need, unfortunately.

Maybe it would be better. You task runs every 15 minutes and then loops through 5 values with a 2 second delay between each. You could have a task using setInterval() that runs every 2 seconds and watches a queue, then just add those 5 items to the queue every 15 minutes.

const symbols = ["ZRXBTC", "ETHBTC", "ETCBTC", "KAVABTC", "AEBTC"];
let queue = [];

const enqueueSymbols = () => {
  symbols.forEach(symbol => queue.push(symbol);
}

const process = () => {
  const symbol = queue.shift();
  if (!symbol) return;

  // do processing for the symbol
}

// interval will check the queue and process ONE entry every 2
// seconds if it finds one
let intervalId = setInterval(process, 2000);

// job will add the list of symbols to the queue every 15 minutes
const j = schedule.scheduleJob('*/15 * * * *', enqueueSymbols);

I think this problem has been made much more complex than it needs to be. The core problem is simple: You never reset cnt to 0 after the first loop. So when the second loop starts, cnt is still greater than the array size, and it exits early! Let's look at fixing this problem first.

The easiest way is to change your schedule.scheduleJob callback to an anonymous function which resets cnt to 0 and then calls callIt() to do the recursive loop again. From your original code, this is one small change to the scheduleJob callback:

const j = schedule.scheduleJob('*/15 * * * *', () => {
  cnt = 0;
  callIt();
});

With this, cnt will be reset to 0 and your code will work repeatedly correctly.

Others have pointed out that async/await is a good way to make this code simpler and I agree. I'll also note that you're using the callback form of your mongodb functions, but all mongodb functions also return promises. Try the above first to confirm it works, then if you'd like, consider the improvements below.

const { MongoClient } = require('mongodb');
const schedule = require('node-schedule');
const fetch = require("node-fetch");

const symbols = ["ZRXBTC", "ETHBTC", "ETCBTC", "KAVABTC", "AEBTC"];

//a descriptive name helps your future self and others understand code easier
const getBTCData = async symbol => {  //make this function accept the current symbol
    //async/await lets us write this much nicer and with less nested indents
    let data = await fetch(`https://api.binance.com/api/v3/klines?symbol=${symbol}&interval=30m&limit=1`).then(res => res.json());
    const btcusdtdata = data.map(d => {
        return {
            Open: parseFloat(d[1]),
            High: parseFloat(d[2]),
            Low: parseFloat(d[3]),
            Close: parseFloat(d[4]),
            Volume: parseFloat(d[5]),
            Timespan: 30,
        }
    });
    console.log(btcusdtdata);
    saveToDatebase(symbol, btcusdtdata);
    //recursive functions are complicated, we can get rid of it here
    //by moving the responsibility to the caller
};

//helper function for an awaitable timeout
const sleep = ms => new Promise(res => setTimeout(res,ms));

const j = schedule.scheduleJob('*/15 * * * *', async () => {
    //expand this function to be responsible for looping the data
    for(let symbol of symbols) {
        //we can pass symbol to getBTCData instead of making it
        //responsible for figuring out which symbol it should get
        await getBTCData(symbol); 
        await sleep(2000);
    }
});

//make this a helper function so `saveToDatabase()` isn't also responsible for it
const getDateTime = () => {
    let today = new Date();
    let date = today.getFullYear() + '-' + (today.getMonth() + 1) + '-' + today.getDate();
    let time = today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds();
    return date + ' ' + time;
};

const saveToDatebase = async (symbol, BTCdata) => {
    const url = 'mongodb+srv://username:[email protected]/<dbname>?retryWrites=true&w=majority';

    let dateTime = getDateTime();
    
    //use await here and below to vastly simplify this function
    let db = await MongoClient.connect(url, { useNewUrlParser: true, useUnifiedTopology: true });
    const dbo = db.db('CryptoCurrencies');
    const myobj = { Name: symbol, Array: BTCdata, Date: dateTime };
    await dbo.collection(symbol).insertOne(myobj);
    console.log('1 document inserted');
    db.close();
};

I haven't tested this code - let me know if there are any errors.


Is there any alternatives to use instead of setTimeout()

Assuming you just want to execute some code every 2 seconds, instead of using a loop, use setTInterval with 2 seconds delay.

setInterval(() => {
   // code here will run every 2 seconds
}, 2000);

EDIT:

I need to call the script every 15 minutes it should go through array and when it call all properties from array it suppose to stop

Here's an example of code that calls run function every 15 seconds and accesses each array element with 2 seconds delay.

First setInterval function calls the run function every 15 seconds and second setInterval function, inside the run function, accesses each array element with 2 seconds delay. After all array elements have been accessed, this interval is cancelled.

const symbols = ["ZRXBTC", "ETHBTC", "ETCBTC", "KAVABTC", "AEBTC"];

console.log('wait 15 seconds before run function is called');
setInterval(() => {
  run();
}, 15000);

function run() {
  console.log('running code after 15 seconds interval');
  let index = 0;
  const id = setInterval(() => {
    console.log(symbols[index]);
    index++;
    
    if (index >= symbols.length) {
      console.log('all array indexes accessed');
      clearInterval(id);
    }
  }, 2000);
}

Tags:

Javascript