Ускоренное выполнение нескольких асинхронных / ожидающих вызовов - PullRequest
2 голосов
/ 28 апреля 2020

Я не могу поделиться своим точным кодом - но я попытался проиллюстрировать мою ситуацию ниже:

У удаленного API есть запрос макс. лимит 10 запросов / чел. c. и я хотел бы ускорить мой код, чтобы приблизиться к этому пределу. В настоящее время код выполняет 1-2 запроса как таковых. c.

Пример - получение 10 часов данных для 100 различных лиц:

(async function get(...) {
    await getPersonData(for one person);
    if (not all persons' data has been fetched) { get(fetch data for the next person); }
})(...);

async function getPersonData() {
    const personData = await getHistoricalData(...);
    ...
};

async function getHistoricalData(...) {

    // Fetch 10 hours of data ...

    while (as long as all data has not yet been fetch...) {
        const data = await getOneHourOfData(...);
        ...
    }
    return all_20_hours_of_data;
} 

async function getOneHourOfData(...) {
    return await remote.api.getData(get 1 hour of data);
}

Пример выше - моя стандартная версия мой код - я пробовал также два разных подхода:

  • , чтобы использовать Promise.all () и получать как 5 человек одновременно
  • , чтобы скопировать / вставить и запустить несколько версий функция get () одновременно (первые 4 строки кода)

оба метода работали - но ни один из них, похоже, не ускоряет ничего ... ?? У меня есть идея, что while-l oop блокирует / замедляет весь процесс?

Ответы [ 3 ]

2 голосов
/ 28 апреля 2020

Код в вашем вопросе выглядит примерно так:

(async function get() {
    try {
        console.time("get");
        console.log(JSON.stringify(await getPersonData()));
        console.timeEnd("get");
    } catch (e) {
        console.error(e);
    }
})();

async function getPersonData() {
    const personData = await getHistoricalData();
    return personData;
};

async function getHistoricalData() {
    const data = [];
    for (let hour = 0; hour < 10; ++hour) {
        data.push(await getOneHourOfData());
    }
    return data;
} 

function delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

let num = 0;
async function getOneHourOfData() {
    await delay(150);
    return ++num;
}

Для запуска требуется около 1500 мс.

Это то же самое, что делать 10-часовые вызовы параллельно и использовать Promise.all:

(async function get() {
    try {
        console.time("get");
        console.log(JSON.stringify(await getPersonData()));
        console.timeEnd("get");
    } catch (e) {
        console.error(e);
    }
})();

async function getPersonData() {
    const personData = await getHistoricalData();
    return personData;
};

async function getHistoricalData() {
    const promises = [];
    for (let hour = 0; hour < 10; ++hour) {
        promises.push(getOneHourOfData()); // <== No `await`!
    }
    return Promise.all(promises); // <== `await `on this line is optional but
                                  // pointless, this is an `async`
                                  // function, so its promise will be
                                  // resolved to the promise we return
} 

function delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

let num = 0;
async function getOneHourOfData() {
    await delay(150);
    return ++num;
}

Он работает примерно за 150 мс, потому что 10 вызовов исторических данных происходят параллельно. Обратите внимание, что главное - создать массив обещаний (без await их), а затем использовать Promise.all, чтобы получить одно обещание для всего этого массива обещаний.

1 голос
/ 28 апреля 2020

Вы можете использовать (очень специальный) семафор, чтобы ограничить вызовы API определенной скоростью:

 class TimeSemaphore {
   #times = []; 
   #backlog = Promise.resolve();

   constructor(interval, parallel) {
     this.interval = interval; this.parallel = parallel;
   }

   async aquire(cb) {
     this.#backlog = this.#backlog.then(() => {
      if(this.#times.length >= this.parallel && Date.now() - this.#times[0] < this.interval)
         return new Promise(res => setTimeout(res, this.interval - (Date.now() - this.#times[0]));
     });

    this.#times.push(Date.now());

    await this.#backlog;

    try {
      return await cb();
    } finally {
      this.#times.shift();
    }
  }
 }

Это можно использовать как:

  const apiLimit = new TimeSemaphore(1000, 5);

  async function callAPI() {
    await apiLimit.aquire(async function() {
      await fetch(...);
     });
  }

  callAPI(); // as often as you want
0 голосов
/ 28 апреля 2020

Я предпочитаю использовать Promise.all.

const data = await Promise.all([
  getOneHourOfData(params)
  ... // the same as above but different params x times
])

Теперь мне очень интересно выражение while (as long as all data has not yet been fetch...) {. Это возможно

await new Promise((resolve, reject) => setTimeout(resolve, 1000))

?

...