首页 > 解决方案 > 在循环中使用异步操作超时

问题描述

问题:我有一个要删除的用户列表。不仅用户本人被删除,而且与他相关的大量数据也被删除。一些涉及删除用户信息的表包含数百万条记录。所有内部进程在删除时都是异步启动的。如果删除一个用户时没有问题,并且后台所有进程都成功(即客户端不期望响应),那么如果您运行其中几个,这会占用所有服务器资源,这只要它被移除,自然会对网站的工作产生负面影响。

for (let i = 0; i < users.length; i++) {
    const user = users[i];
    await this.removeInstance(user, removedBy);
}

this.removeInstance包含许多其他异步操作。

我想要的:每隔 3 分钟启动一个异步操作this.removeInstance 。数据删除的速度对我来说并不重要,但根据我的观察,1-2 分钟就足以“完全完成”一个用户。

注意: this.removeInstance 返回已删除的用户,但我不能使用它,因为已经有十几个进程在后台运行以清除他的信息。

谢谢你的帮助!

标签: node.jsmongodbexpressasync-await

解决方案


您可以创建一个包装函数,将异步任务的完成延迟到最低限度:

const wait = ms =>
  new Promise(resolve => setTimeout(resolve, ms));

const delayCompletion = minTime => task =>
  Promise.allSettled([task(), wait(minTime)])
    .then(([result]) => result.status === "fulfilled"  
      ? result.value 
      : Promise.reject(result.reason)
    );

例子:

const wait = ms =>
  new Promise(resolve => setTimeout(resolve, ms));

const delayCompletion = minTime => task =>
  Promise.allSettled([task(), wait(minTime)])
    .then(([result]) => result.status === "fulfilled"  
      ? result.value 
      : Promise.reject(result.reason)
    );
      

//showcase:

async function main() {
  console.log("-- start loop --");
  for(let i = 0; i < 3; i++) {
    console.log(`start iteration ${i}`);
    
    const delayAtLeastTwoSeconds = delayCompletion(2000);
    const oneSecondtask = () => wait(1000);
    
    await delayAtLeastTwoSeconds(oneSecondtask);
    
    console.log(`finish iteration ${i}`);
  }
  console.log("-- finish loop --");
}

main();

Promise.allSettled()等到所有的 Promise 都不再挂起。我们给它一个异步任务,它会添加另一个以最小延迟,所以如果task提前完成,那么它仍然必须等到延迟计时器完成。相反,如果延迟计时器首先完成但task尚未完成,则仍将等待。

Promise.allSettled解析为一组承诺,其中每个承诺都有一个状态"fulfilled""rejected"。我们只对数组的第一项感兴趣,因为它是task- 第二项是延迟。如果成功,我们只返回它的值,如果不成功则使用原始拒绝原因拒绝。这种方式delayCompletion仍然保留与原始承诺相同的语义,并且任何使用原始承诺的代码都可以透明地使用来自delayCompletion.

在某些方面,这与Promise.race()首先解决的承诺相反。相反,我们等待最后一个,但我们确实有一个固定的返回值。

使用此辅助函数,您的代码可以像这样转换:

const safeDelay = delayCompletion(3 * 60 * 1000); //3 minutes

for (let i = 0; i < users.length; i++) {
    const user = users[i];
    await safeDelay(() => this.removeInstance(user, removedBy));
}

推荐阅读