Как загружать несколько URL-адресов снова и снова параллельно в Node.JS

#javascript #node.js

#javascript #node.js

Вопрос:

У меня дома небольшая настройка IoT, и мне нужно повторно запрашивать небольшое количество RESTful API для получения данных с нескольких датчиков. Каждый URL-адрес должен запрашиваться с использованием пользовательской задержки между различными попытками.

Каков наилучший способ реализовать это в Node.JS ? Конечная цель — проанализировать возвращенный JSON и сохранить его в Realm DB.

Я пытаюсь реализовать это в promises и изо всех сил пытаюсь придумать две вещи: 1) куда я должен поместить then() для обработки ответа сервера (имитируемого как setTimeout), 2) как перенести URL-адрес после тайм-аута.

 const conf = [
  {
    url: "http://google.com",
    timeout: 2400, // ms between queries
  },
  {
    url: "http://duckduckgo.com",
    timeout: 2000,
  }
];

let activePromises = [];

conf.forEach( function(el) {
  activePromises.push( new Promise(
    function webHandler(resolve, reject) {
      console.log(`Starting request to ${ el.url }...`);
      setTimeout( () => {
        console.log(`Resolving ${ el.url } after ${ el.timeout }ms`);
        resolve('body'); // real-world contents will be a JSON document
        // how to repeat the same API query after el.timeout ms???
      }, 30);
    }
  ));
});
 

Ответ №1:

Используйте Promise.all() вместе с map()

 const conf = [
 {
   url: "http://google.com",
   timeout: 2400, // ms between queries
 },
 {
   url: "http://duckduckgo.com",
   timeout: 2000,
 }
];

Promise.all(conf.map(async item=> await doTask(item.url,item.timeout)))

const doTask=(url,timeout)=>{
  return new Promise((resolve, reject)=> {
   ..... 
 }
}
 

Комментарии:

1. он не показывает, как на самом деле обрабатывать разное время ожидания для каждого обещания, имеет бесполезный конструктор new Promise, которого там не должно быть, и Promise.all используется без всякой причины

Ответ №2:

Если вам нужно повторять действие каждые n секунд, вы ищете setInterval

 const conf = [
  {
    url: "http://google.com",
    timeout: 2400, // ms between queries
  },
  {
    url: "http://duckduckgo.com",
    timeout: 2000,
  }
];

const fetchResult = url => api(url) //pseudo code here

let results = Array(conf.length).fill(Promise.resolve(null))

conf.foreach(({url, timeout}, i) => {
  setInterval(async() => {
    const res =  await fetchResult(url)
    results[i] = res;
  }, timeout)
})


 

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

Комментарии:

1. как можно использовать постоянно изменяющийся массив обещаний, на которые вы ссылаетесь? когда вы хотите вызвать Promise.all?

Ответ №3:

Допустим (для простоты) loadData , это функция, которая будет извлекать данные из URL-адреса (который вы в настоящее время смоделировали с использованием таймаута).

Если вы хотите начать отправлять запросы через регулярные промежутки времени, вы можете просто использовать setInterval:

 conf.forEach(el => {
   setInterval(() => {
      loadData(el.url)
         .then((body) => { /** Save to DB here */ });
         .catch(() => { /** Handle error */ });
   }, el.timeout);
});
 

Но если вы хотите дождаться разрешения loadData, а затем дождаться истечения времени ожидания, вы можете сделать что-то вроде:

 function process() {
   loadData(el.url).then((body) => {
      /** Save to DB here */
      setTimeout(process, el.timeout);
   }).catch(() => { /** Handle error */ });
}
 

который вызовет ту же функцию по истечении указанного времени ожидания.

Комментарии:

1. Ваш первый пример верен. Во втором примере должно учитываться произвольное количество then секунд, setTimeout так как сохранение в БД является асинхронной задачей. Самое главное, setTimeout это не рекурсия и не увеличит стек вызовов

2. @Max спасибо за предложение, я не знал о концепции асинхронных стеков, и когда я выполнил это и просмотрел в инструментах разработки, подумал, что это фактический стек вызовов. Я отредактировал свой ответ.