首页 > 解决方案 > async await 在 setTimeout 里面 forEach

问题描述

我需要在nodejs中安排一些后台作业,所以我需要一个作业在前一个完成后运行。

在一个函数中,我有下面的代码。

jobArray.forEach((item, i) => {
  setTimeout(async () => {
    await collectChannelsData(item)
  }, i * 10000);
});
console.log('go to 2nd job')

jobArray.forEach((item, i) => {
  setTimeout(async () => {
    await collectVideosData(item)
  }, i * 10000);
});
console.log('finish')

问题是,因为 setTimeout 是一个异步函数,所以 forEach 不会等待它完成,而是继续第二个工作。有什么可能的解决方案吗?

先感谢您

编辑:一种可能的解决方案是将我的代码转换为:

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

for (const job of jobArray) {
  await collectChannelsData(job) 
  await wait(10000);
}
for (const job of jobArray) {
  await collectVideosData(job) 
  await wait(10000);
}

但我正在寻找一种方法,其中包括 setTimeout 和 forEach。

标签: javascriptnode.jsasync-await

解决方案


注意:这个问题最初询问“我正在寻找更好的方法”,它最初不包含使用await wait(10000). 我为那个原始问题写了这个答案(在它被编辑之前)。此后,该问题已被编辑为一个不同的问题。

但我正在寻找一种方法,其中包括 setTimeout 和 forEach。

.forEach()如果不涉及循环外的变量并可能链接承诺, 则没有干净的方法可以做到这一点。.forEach()不以任何方式感知承诺,因此当您想要对循环的异步迭代进行排序时,通常最好放弃使用它。

对于此类问题,使用for循环是更好的选择之一。await它满足了对异步操作进行排序的主要目标,在一个异步操作的完成和下一个异步操作的开始之间具有特定的延迟。它简单、可读、高效、易于维护,它为调用者提供了一种了解一切何时完成的方法,并将异步错误返回给调用者。您的.forEach()循环不符合您的目标或做许多其他事情。

您可以将计时器变成一个承诺返回函数,您可以使用await它:

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

async function run() {

    for (let item of jobArray) {
        await collectChannelsData(item)
        await delay(10000);
    }
    // go to second job now that first job is finished
    for (let item of jobArray) {
        await collectVideosData(item);
        await delay(10000);
    }
}

run().then(() => {
   console.log("all done");
}).catch(err => {
   console.log(err);
});

正如您可能已经知道的那样,.forEach()它不会关注您传递给它的回调返回的内容,因此它完全不感知承诺,因此在使用承诺时确实没有任何位置。常规for循环非常有用,因为它会在使用await.


有一些方法可以.forEach()使用在循环外部声明的更高范围的 Promise 来破解事情,但这些选项根本不干净,所以我不会费心在这里展示它们。


在我们拥有 之前await,我们曾经使用.reduce()对 promises 进行排序。这会创建一系列按顺序调用其函数的 Promise:

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

// use .reduce() to chain promises so async operations are sequenced
jobArray.reduce(p, item => {
    return p.then(() => {
         return collectChannelsData(item).then(() => delay(10000));
    });
}, Promise.resolve()).then(() => {
    jobArray.reduce(p, item => {
        return p.then(() => {
            return collectVideosData(item).then(() => delay(10000));
        });
    }, Promise.resolve());
}).then(() => {
   console.log("all done");
}).catch(err => {
   console.log(err);
});

但希望您能看到实现看起来多么清晰,await因为所有.reduce()脚手架都妨碍了查看控制流。


推荐阅读