setTimeout внутри al oop, останавливает работу скрипта - PullRequest
11 голосов
/ 19 июня 2020

Мой скрипт автоматически получает данные от API и сохраняет их в MongoDB. Мне нужно было создать задержку не менее 2 секунд, прежде чем получать одни данные за другими. Проблема в том, что мой скрипт перестал работать второй раз. Допустим, у меня скрипт работает каждый час, я включаю скрипт в 14.00 - он работает, а в 15.00 - останавливается. Я начинаю исследовать проблему и прихожу к выводу, что проблема с setTimeout() внутри al oop.

Это статья, которую я нашел Остерегайтесь при использовании SetTimeout() в For L oop # JS

Эта строка Пакет Node-Schedule Он в основном вызывает скрипт каждые 15 минут (если кому-то интересно, что это такое)

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

Моя цель: как я могу изменить свой код, чтобы он все еще имел задержку в 2 секунды и рабочий л oop. Есть ли альтернативы для использования вместо setTimeout(), возможно, мне просто нужно поместить setTimeout() в другое место в коде, или, может быть, есть даже какие-то пакеты, которые я могу добавить дополнительно.

Проблема c область кода:

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

ПОЛНЫЙ КОД

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

};

РЕДАКТИРОВАТЬ1: Чтобы быть более правильным Уточню задачу. Мой пакет node-schedule предполагает вызывать сценарий каждые 15 минут, а я хочу вызывать свойства в массиве каждые 2 секунды. Мне нужно вызывать свойства из массива каждые 2 секунды, иначе я получу IP-запрет от Binance API для вызова API на много / быстро.

EDIT2 Хорошо. setInterval() для меня не решение. Поскольку мне нужно вызывать скрипт каждые 15 минут, он должен go через массив, и когда он вызывает все свойства из массива, он должен остановиться. В setInterval() после вызова всех свойств в массиве он снова запускается, к сожалению, это не то, что мне нужно.

EDIT3: Я протестировал несколько вариантов из ответов ниже, все они приводят меня к та же проблема, что сценарий не может запуститься во второй раз или сценарий начинает работать сразу или повторяется даже после завершения свойств массива. Тем не менее, спасибо за ответы, но моя проблема все еще одна.

В настоящее время я пытаюсь использовать методы async / await. Но я получаю сообщение об ошибке: await is only valid in async function

EDIT4 : Итак, это решение с полным кодом от @yoavmatchulsky. Скрипт начинает работать, но я не получаю никаких данных или чего-то такого. Это похоже на работу - но ничего не происходит. Ошибок нет, ничего.

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

Ответы [ 8 ]

4 голосов
/ 19 июня 2020

Я думаю, что эта проблема стала намного сложнее, чем нужно. Основная проблема проста: Вы никогда не сбрасываете cnt в 0 после первого l oop. Поэтому, когда начинается второй l oop, cnt все еще больше, чем размер массива, и он завершается раньше! Давайте сначала рассмотрим решение этой проблемы.

Самый простой способ - изменить обратный вызов schedule.scheduleJob на анонимную функцию, которая сбрасывает cnt на 0, а затем вызывает callIt() для выполнения рекурсивного l oop очередной раз. Из исходного кода это небольшое изменение в обратном вызове scheduleJob:

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

При этом cnt будет сброшен на 0, и ваш код будет многократно работать правильно.

Другие отмечали, что async/await - хороший способ упростить этот код, и я согласен. Я также отмечу, что вы используете форму обратного вызова своих функций mongodb, но все функции mongodb также возвращают обещания. Сначала попробуйте описанную выше , чтобы убедиться, что она работает, затем, если хотите, рассмотрите приведенные ниже улучшения.

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

Я не тестировал этот код - дайте мне знать, если есть ошибки.

3 голосов
/ 19 июня 2020

Есть ли альтернативы для использования вместо setTimeout ()

Предполагая, что вы просто хотите выполнять какой-то код каждые 2 секунды, вместо использования al oop используйте setTInterval с задержкой в ​​2 секунды.

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

EDIT:

Мне нужно вызывать скрипт каждые 15 минут, он должен go через массив и когда он вызывает все свойства из массива, он Предположим, чтобы остановить

Вот пример кода, который вызывает функцию run каждые 15 секунд и обращается к каждому элементу массива с задержкой в ​​2 секунды.

Первая функция setInterval вызывает run каждые 15 секунд, а вторая функция setInterval внутри функции run обращается к каждому элементу массива с задержкой в ​​2 секунды. После обращения ко всем элементам массива этот интервал отменяется.

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);
}
1 голос
/ 19 июня 2020

Хорошо. setInterval () для меня не решение. Поскольку мне нужно вызывать скрипт каждые 15 минут, он должен go через массив, и когда он вызывает все свойства из массива, он должен остановиться. В setInterval () после вызова всех свойств в массиве он снова запускается, к сожалению, это не то, что мне нужно.

Может быть, было бы лучше. Ваша задача запускается каждые 15 минут, а затем перебирает 5 значений с задержкой в ​​2 секунды между каждым. У вас может быть задача с использованием setInterval (), которая запускается каждые 2 секунды и наблюдает за очередью, а затем просто добавляет эти 5 элементов в очередь каждые 15 минут.

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);
0 голосов
/ 19 июня 2020

Вместо рекурсивных вызовов вы можете использовать forEach и заранее планировать все setTimeout задачи.

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

symbols.forEach((symbol, index) => {
  setTimeout(() => {
    // your code here
    console.log(index, symbol);
  }, index * 2000);
});

Обратите внимание, что есть небольшая разница. Это позволит запланировать запуск задач на расстоянии 2000 мс друг от друга. Между задачами нет 2000 мс.

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

Если вы хотите 2000 мс между задачами, все немного сложнее. Вам нужно будет управлять очередью и добавлять задержку 2000 мс между задачами. Вот пример того, как это может выглядеть:

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

Это позволит запланировать начало каждой задачи за 2000 мс от конца предыдущей задачи.

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

Как указано в комментариях, здесь как вы можете объединить вышеперечисленное с фрагментом кода вопроса. Здесь используется первое решение, но вы можете сделать то же самое для второго.

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);
});
0 голосов
/ 19 июня 2020

проверю saveToDatebase(btcusdtdata); функция

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();
0 голосов
/ 19 июня 2020

Вот решение 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));
}
0 голосов
/ 19 июня 2020

Если вы знакомы с async / await , вы можете использовать вспомогательный метод для «сна»:

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

А затем использовать его и свой callIt метод с внешний l oop:

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

просто обязательно удалите setTimeout in callIt

EDIT - Пример полного кода:

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);
0 голосов
/ 19 июня 2020

Попробуйте использовать setInterval.

setInterval (callIt, 2000);

, но поместите его вне функции callIt.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...