【问题标题】:Speed-up execution of multiple async/await calls加速执行多个 async/await 调用
【发布时间】:2020-04-28 06:34:58
【问题描述】:

我无法分享我的确切代码 - 但我已尝试在下面说明我的情况:

远程 API 有一个请求最大值。限制为 10 个请求/每秒。我想加快我的代码接近这个限制。目前该代码每秒运行 1-2 个请求。

示例 - 为 100 个不同的人获取 10 小时的数据:

(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 循环阻塞/减慢了整个过程?

【问题讨论】:

  • 您所说的您尝试过的两件事都应该有效,这表明实施这些想法时存在问题。我们无法为您提供我们看不到的代码。 :-) 请向我们展示您尝试用于这些的代码。请发布真实代码,而不是伪代码,最好通过Stack Snippets 运行。当然,它不一定是您的真实代码,您可能希望用 setTimeout 模拟 remote.api.getData,但将其设为真实代码可以消除歧义并帮助我们为您提供帮助。
  • 我完全理解。但由于我设法使所有代码示例都能正常工作——但它们并没有加快整个过程......然后我认为一个 psydo-code/concept 可以做到这一点。但是好吧 - 如果我在上面的例子中没有犯任何明显的错误,我会尝试提出更具体的东西:)
  • 如果我们看到真实的代码,我们总是可以提供更好的答案,而且对您的主要好处是我们甚至可以提供您甚至没有想过要求的建议/改进。出于某种未知的原因,在这里发帖的人认为他们应该用伪代码提出一个通用问题——这对你来说几乎不是最好的选择。请始终显示相关的真实代码,我们可以更准确地为您提供帮助,并提供您可以使用的实际代码,并且可以以您甚至不知道要问的方式为您提供帮助。

标签: javascript node.js performance parallel-processing async-await


【解决方案1】:

您问题中的代码实际上如下所示:

(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 次历史数据调用是并行发生的。请注意,关键是建立一个 promise 数组(没有 awaiting 它们),然后使用 Promise.all 为整个 promise 数组获取一个 promise。

【讨论】:

  • 啊!当我尝试使用 Promise.all() 时,我没有在 getOneHourOfData 函数上使用它 - 但在 get() 函数上......我猜 - 这一定是它工作的原因 - 但没有任何速度改进!谢谢你,我的朋友!我会尝试一下并尽快回复您! :) 再次感谢你!顺便说一句-您是否亲自使用 Promise.all(一天中所有您一生的任务)...否则您如何有时间获得 147 个金徽章!?哈哈!令人印象深刻!
  • @PabloDK - 哈哈!如果你在get 中也这样做,它会起作用,但关键是在不等待它们的情况下建立一组承诺,然后awaitPromise.all 的结果。编码愉快!
  • 我今天早些时候意识到我使用的 API 客户端有一个内置的 max-rate-limit,它在每个 API 调用之间添加了 500 毫秒的暂停!所以难怪我不能让我的代码快速运行!哈哈!远程 API 允许每秒 20 次调用 - 控制它的最佳方式/最佳实践是什么?目前,我已将内置速率限制设置为在每次调用后暂停 50 毫秒(1000 毫秒/50 毫秒 = 20 次调用/秒)...然后我开始并执行 50 个同时获取承诺和 promise.all(全部 50)在我之前加载下一个 50 ......它似乎工作......但我想它仍然可以改进?
  • @PabloDK - 对我来说听起来很合理。 :-)
  • 好吧 :-) 你不会添加一些额外的层来控制这个流程吗?可能是通过一些快速的 NPM 模块或类似的? :-)
【解决方案2】:

您可以使用(非常特殊的)信号量来将 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

【讨论】:

  • 它看起来很花哨 - 您能否详细说明代码的作用以及如何...我不清楚 :-)
【解决方案3】:

我更喜欢使用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))

?

【讨论】:

  • 不 - 这只是另一种说法:在每次迭代中提取 1 小时的数据,直到获取所有数据(示例中为 10 次迭代 / 10 小时)。
猜你喜欢
  • 2015-01-20
  • 2019-05-24
  • 1970-01-01
  • 1970-01-01
  • 2021-09-30
  • 2020-11-22
  • 1970-01-01
  • 2021-03-19
  • 2019-02-04
相关资源
最近更新 更多