简体   繁体   English

循环内的setTimeout,停止脚本工作

[英]setTimeout inside a loop, stops script from working

My script is receiving data from API and store in MongoDB automatically.我的脚本正在从 API 接收数据并自动存储在 MongoDB 中。 I was needed to create a at least 2 second delay before receiving one data after another.在接收一个接一个的数据之前,我需要创建至少 2 秒的延迟。 The problem is that my script is stop working on second time.问题是我的脚本第二次停止工作。 Let's say my script working every hour, I enable the script at 14.00 - it works and at 15.00 - it stops.假设我的脚本每小时工作一次,我在 14.00 启用脚本 - 它工作,在 15.00 - 它停止。 I start researching the problem and come to the point that is problem with setTimeout() inside a loop.我开始研究这个问题,并指出循环内setTimeout()的问题。

This is an article what I found Watch Out When Using SetTimeout( ) in For Loop #JS这是我在 For Loop #JS 中使用SetTimeout( ) 时发现的一篇文章

This line is a Node-Schedule package It basically calls the script every 15th minutes (if someone is wondering that it is)这一行是Node-Schedule package它基本上每 15 分钟调用一次脚本(如果有人想知道的话)

const j = schedule.scheduleJob('*/15 * * * *', callIt)

My goal: How can I change my code to still having a 2 second's delay and working loop.我的目标:如何将我的代码更改为仍然有 2 秒的延迟和工作循环。 Is there any alternatives to use instead of setTimeout() , maybe I just need to put a setTimeout() in another place in a code or maybe there is even some type of packages that I can add additional.是否有任何替代方法可以代替setTimeout() ,也许我只需要将setTimeout()放在代码中的另一个位置,或者甚至可以添加某种类型的包。

Problematic code area:有问题的代码区域:

    var symbols = ["ZRXBTC", "ETHBTC", "ETCBTC", "KAVABTC", "AEBTC"]
];
let cnt = 0;

const callIt = () => {

    fetch(`https://api.binance.com/api/v3/klines?symbol=${symbols[cnt]}&interval=30m&limit=1`)
        .then(res => res.json())
        .then(data => {
            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(btcusdtdata);
            cnt++;
            if (cnt < symbols.length) setTimeout(callIt, 2000)
        })
        .catch((err) => {
            console.log(err);
        })
};

FULL CODE完整代码

var requestPromise = require('request-promise');
const { MongoClient } = require('mongodb');
const schedule = require('node-schedule');
var XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;
const fetch = require("node-fetch");

var symbols = ["ZRXBTC", "ETHBTC", "ETCBTC", "KAVABTC", "AEBTC"]
    ];
    let cnt = 0;

    const callIt = () => {

        fetch(`https://api.binance.com/api/v3/klines?symbol=${symbols[cnt]}&interval=30m&limit=1`)
            .then(res => res.json())
            .then(data => {
                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(btcusdtdata);
                cnt++;
                if (cnt < symbols.length) setTimeout(callIt, 2000)
            })
            .catch((err) => {
                console.log(err);
            })
    };

const j = schedule.scheduleJob('*/15 * * * *', callIt)

const saveToDatebase = function(BTCdata) {

    const url = 'mongodb+srv://username:password@cluster0-1kunr.mongodb.net/<dbname>?retryWrites=true&w=majority';

    var today = new Date();
    var date = today.getFullYear() + '-' + (today.getMonth() + 1) + '-' + today.getDate();
    var time = today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds();
    var dateTime = date + ' ' + time;

    MongoClient.connect(url, { useNewUrlParser: true, useUnifiedTopology: true }, (err, db) => {
        if (err) throw err;
        const dbo = db.db('CryptoCurrencies');
        const myobj = { Name: symbols[cnt - 1], Array: BTCdata, Date: dateTime };
        dbo.collection(`${symbols[cnt - 1]}`).insertOne(myobj, (error, res) => {
            if (error) throw error;
            console.log('1 document inserted');
            db.close();
        });
    });

};

EDIT1: To be more correct I'll specify the task more accurate. EDIT1:为了更正确,我将更准确地指定任务。 My node-schedule package suppose to call the script every 15 minutes and I want to call properties in array every 2 seconds.我的节点计划 package 假设每 15 分钟调用一次脚本,我想每 2 秒调用一次数组中的属性。 I need to call properties from array every 2 seconds otherwise I will receive IP ban from Binance API for calling the API to much/fast.我需要每 2 秒调用一次数组中的属性,否则我将收到来自 Binance API 的 IP 禁令,用于调用 API。

EDIT2 Alright. EDIT2好的。 setInterval() is not a solution for me. setInterval()对我来说不是解决方案。 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.由于我需要每 15 分钟调用一次脚本,它应该通过数组 go 并且当它从数组调用所有属性时它应该停止。 In setInterval() after calling the all properties in array in starts it again, this is doesn't what I need, unfortunately.setInterval()中调用数组中的所有属性后再次启动它,不幸的是,这不是我需要的。

EDIT3: I tested few options from answers below, all of them get me to the same problem that the script cannot start the second time or the script is starts working immediately or repeats even after array properties are ended. EDIT3:我从下面的答案中测试了几个选项,它们都让我遇到了同样的问题,即脚本无法第二次启动,或者脚本立即开始工作,或者即使在数组属性结束后也会重复。 Still thank you for answers, but my problem is still one.仍然感谢您的回答,但我的问题仍然是一个。

Currently I trying to use async/await methods.目前我正在尝试使用 async/await 方法。 But I receive an error that await is only valid in async function但是我收到一个错误, await is only valid in async function

EDIT4 : So this is a fullcode solution from @yoavmatchulsky. EDIT4 :所以这是来自@yoavmatchulsky 的完整代码解决方案。 Script starts working, but I don't receive any data or something.脚本开始工作,但我没有收到任何数据或其他东西。 It's just like working - but nothing happening.这就像工作一样——但什么也没有发生。 No errors, nothing.没有错误,什么都没有。

var requestPromise = require('request-promise');
const { MongoClient } = require('mongodb');
const schedule = require('node-schedule');
var XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;
const fetch = require("node-fetch");

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

const sleep = async(timeout) => {
    return new Promise(resolve => {
        setTimeout(resolve, timeout);
    });
}

const callIt = async(symbol) => {
    return fetch(`https://api.binance.com/api/v3/klines?symbol=${symbol}&interval=30m&limit=1`)
        .then(res => res.json())
        .then(data => async() => {
            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);
            await saveToDatebase(btcusdtdata);
        })
        .catch((err) => {
            console.log(err);
        })
};

const saveToDatebase = async function(BTCdata) {
    return new Promise((resolve, reject) => {

        const url = 'mongodb+srv://username:password@cluster0-1kunr.mongodb.net/<dbname>?retryWrites=true&w=majority';

        var today = new Date();
        var date = today.getFullYear() + '-' + (today.getMonth() + 1) + '-' + today.getDate();
        var time = today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds();
        var dateTime = date + ' ' + time;

        MongoClient.connect(url, { useNewUrlParser: true, useUnifiedTopology: true }, (err, db) => {
            if (err) {
                return reject(err);
            }
            const dbo = db.db('CryptoCurrencies');
            const myobj = { Name: symbols[cnt - 1], Array: BTCdata, Date: dateTime };
            dbo.collection(`${symbols[cnt - 1]}`).insertOne(myobj, (error, res) => {
                if (error) {
                    return reject(error);
                }
                console.log('1 document inserted');
                db.close();
                resolve();
            });
        });
    });
};

const run = async() => {
    let cnt = 0;
    while (cnt < symbols.length) {
        await callIt(symbols[cnt]);
        await sleep(2000);
        cnt++;
    }
}

const j = schedule.scheduleJob('*/2 * * * *', run);

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 .核心问题很简单:在第一个循环之后你永远不会将cnt重置为 0 So when the second loop starts, cnt is still greater than the array size, and it exits early.所以当第二个循环开始时, cnt仍然大于数组大小,并且提前退出。 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.最简单的方法是将您的schedule.scheduleJob回调更改为匿名 function 将cnt重置为 0,然后调用callIt()再次执行递归循环。 From your original code, this is one small change to the scheduleJob callback:从您的原始代码中,这是对scheduleJob回调的一个小改动:

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

With this, cnt will be reset to 0 and your code will work repeatedly correctly.这样, cnt将被重置为 0,您的代码将重复正常工作。

Others have pointed out that async/await is a good way to make this code simpler and I agree.其他人指出async/await是使代码更简单的好方法,我同意。 I'll also note that you're using the callback form of your mongodb functions, but all mongodb functions also return promises.我还要注意,您正在使用 mongodb 函数的回调形式,但所有 mongodb 函数也返回承诺。 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:password@cluster0-1kunr.mongodb.net/<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()是否有任何替代方法可以代替 setTimeout()

Assuming you just want to execute some code every 2 seconds, instead of using a loop, use setTInterval with 2 seconds delay.假设您只想每 2 秒执行一次代码,而不是使用循环,请使用具有 2 秒延迟的setTInterval

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我需要每 15 分钟调用一次脚本,它应该 go 通过数组调用,当它从数组调用所有属性时它应该停止

Here's an example of code that calls run function every 15 seconds and accesses each array element with 2 seconds delay.这是一个代码示例,它每 15 秒调用run function,并以 2 秒的延迟访问每个数组元素。

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. 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); }

Alright.好吧。 setInterval() is not a solution for me. setInterval() 对我来说不是解决方案。 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.由于我需要每 15 分钟调用一次脚本,它应该通过数组 go 并且当它从数组调用所有属性时它应该停止。 In setInterval() after calling the all properties in array in starts it again, this is doesn't what I need, unfortunately.在 setInterval() 中调用数组中的所有属性后再次启动它,不幸的是,这不是我需要的。

Maybe it would be better.也许会更好。 You task runs every 15 minutes and then loops through 5 values with a 2 second delay between each.您的任务每 15 分钟运行一次,然后循环遍历 5 个值,每个值之间有 2 秒的延迟。 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.您可以使用每 2 秒运行一次并监视队列的 setInterval() 任务,然后每 15 分钟将这 5 个项目添加到队列中。

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);

Try using a setInterval.尝试使用 setInterval。

setInterval(callIt, 2000); setInterval(callIt, 2000);

but put it outside the callIt function.但把它放在外面叫它function。

https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval

If you're familiar with async/await , you can use a helper method to "sleep":如果你熟悉async/await ,你可以使用一个辅助方法来“sleep”:

const sleep = async (timeout) => {
    return new Promise(resolve => {
        setTimeout(resolve, timeout);
    });
}

And then use it and your callIt method with an outside loop:然后在外部循环中使用它和你的callIt方法:

while (cnt < symbols.length) {
    await callIt();
    await sleep(2000);
}

just be sure to remove the setTimeout in callIt只需确保删除callIt中的setTimeout

EDIT - Full code example:编辑 - 完整代码示例:

var requestPromise = require('request-promise');
const { MongoClient } = require('mongodb');
const schedule = require('node-schedule');
var XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;
const fetch = require("node-fetch");

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

const sleep = async (timeout) => {
    return new Promise(resolve => {
        setTimeout(resolve, timeout);
    });
}

const callIt = async (symbol) => {
    return fetch(`https://api.binance.com/api/v3/klines?symbol=${symbol}&interval=30m&limit=1`)
        .then(res => res.json())
        .then(data => async {
            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);
            await saveToDatebase(btcusdtdata);
        })
        .catch((err) => {
            console.log(err);
        })
};

const saveToDatebase = async function(BTCdata) {
    return new Promise((resolve, reject) => {

        const url = 'mongodb+srv://username:password@cluster0-1kunr.mongodb.net/<dbname>?retryWrites=true&w=majority';

        var today = new Date();
        var date = today.getFullYear() + '-' + (today.getMonth() + 1) + '-' + today.getDate();
        var time = today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds();
        var dateTime = date + ' ' + time;

        MongoClient.connect(url, { useNewUrlParser: true, useUnifiedTopology: true }, (err, db) => {
            if (err) {
                return reject(err);
            }
            const dbo = db.db('CryptoCurrencies');
            const myobj = { Name: symbols[cnt - 1], Array: BTCdata, Date: dateTime };
            dbo.collection(`${symbols[cnt - 1]}`).insertOne(myobj, (error, res) => {
                if (error) {
                    return reject(error);
                }
                console.log('1 document inserted');
                db.close();
                resolve();
            });
        });
    });
};

const run = async () => {
    let cnt = 0;
    while (cnt < symbols.length) {
        await callIt(symbols[cnt]);
        await sleep(2000);
        cnt++;
    }
}

const j = schedule.scheduleJob('*/15 * * * *', run);

Here is a async / await solution:这是一个async / await解决方案:

const callIt = async () => {
  try {
    let res = await fetch(
      `https://api.binance.com/api/v3/klines?symbol=${symbols[cnt]}&interval=30m&limit=1`
    );
    let data = await 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(btcusdtdata);
    cnt++;
    if (cnt < symbols.length) {
      await sleep(2000);
      callIt();
    }
  } catch (err) {
    console.log(err);
  }
};

function sleep(ms) {
  return new Promise(res => setTimeout(res, ms));
}

check you saveToDatebase(btcusdtdata);检查你saveToDatebase(btcusdtdata); function function

 var symbols = ['ZRXBTC', 'ETHBTC', 'ETCBTC', 'KAVABTC', 'AEBTC']; let cnt = 0; const callIt = () => { console.log("count", cnt); fetch( `https://api.binance.com/api/v3/klines?symbol=${ symbols[cnt] }&interval=30m&limit=1`, ).then(res => res.json()).then(data => { 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(btcusdtdata); cnt++; if (cnt < symbols.length) setTimeout(callIt, 2000); }).catch(err => { if (cnt < symbols.length) setTimeout(callIt, 2000); console.log('Error => ', err); }); }; callIt();

Instead of making recursive calls you could use forEach and plan all setTimeout tasks ahead of time.您可以使用forEach并提前计划所有setTimeout任务,而不是进行递归调用。

 var symbols = ["ZRXBTC", "ETHBTC", "ETCBTC", "KAVABTC", "AEBTC"]; symbols.forEach((symbol, index) => { setTimeout(() => { // your code here console.log(index, symbol); }, index * 2000); });

Note that there is a slight difference.请注意,存在细微差别。 This will schedule the start of the tasks 2000 ms from each other.这将安排彼此相距 2000 毫秒的任务开始。 There is not 2000 ms between the tasks.任务之间没有 2000 毫秒。

|> task 1 <|                |> task 2 <|
|<-------- 2000 ms -------->|

If you want 2000 ms between the tasks things are a bit more complicated.如果你想要两个任务之间有 2000 毫秒的时间,事情就有点复杂了。 You'll have to manage a queue and add 2000 ms delay between the tasks.您必须管理一个队列并在任务之间添加 2000 毫秒的延迟。 Here is an example of how this might look:这是一个看起来如何的示例:

 function createThrottler(delayMs) { const sleep = () => new Promise(resolve => setTimeout(resolve, delayMs)); let queue; return function (task) { queue = queue? queue.then(sleep): Promise.resolve(); queue = queue.then(task); return queue; }; } var symbols = ["ZRXBTC", "ETHBTC", "ETCBTC", "KAVABTC", "AEBTC"]; const throttle = createThrottler(2000); symbols.forEach((symbol, index) => { throttle(() => { // you code here console.log(index, symbol); }); });

This this will schedule the start of each tasks 2000 ms from the end of the previous task.这将安排每个任务的开始时间距上一个任务结束 2000 毫秒。

|> task 1 <|                           |> task 2 <|
           |<-------- 2000 ms -------->|

As requested in the comments, here is how you can combine the above with the code snippet of the question.根据评论中的要求,您可以将上述内容与问题的代码片段结合起来。 This uses the first solution, but you could do the same for the second.这使用第一个解决方案,但您可以对第二个解决方案执行相同的操作。

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

symbols.forEach((symbol, index) => {
  setTimeout(() => {
    fetch(`https://api.binance.com/api/v3/klines?symbol=${symbol}&interval=30m&limit=1`)
    .then(res => res.json())
    .then(data => {
      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(btcusdtdata);
    })
    .catch((err) => {
      console.log(err);
    });
  }, index * 2000);
});

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM