我需要在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的方法。
答案 0 :(得分:1)
注意:这个问题最初是问 “我正在寻找更好的方法” ,它最初不包含使用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()
并没有关注传递给它的回调返回的内容,因此它是完全不承诺的,因此在使用promises时确实没有任何位置更多。常规的for
循环是如此有用,因为使用await
时它将暂停循环。
有一些方法可以使用在循环外部声明的范围更大的承诺来使用.forEach()
入侵事物,但是这些选项根本不是干净的,因此我不会在这里显示它们。
在拥有await
之前,我们曾经使用.reduce()
对诺言进行排序。这会创建一个promise链,这些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()
支架都妨碍了控制流的实现。
答案 1 :(得分:0)
如果您真的想使用forEach
方法,则必须编写自己的forEach
异步版本
(async function() {
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
async function forEach(arr, cb) {
for (const item of arr) {
await cb(item);
}
}
await forEach([1, 2, 3], async function(item) {
await wait(500);
console.log("Step" + item);
});
console.log("Done");
})()
答案 2 :(得分:-1)
我不确定您为什么显式地使用forEach
,因为它不是很漂亮。但是绝对可以使用forEach
。
let queue = Promise.resolve();
jobArray.forEach(job => {
queue = queue.then(() => collectChannelsData(job));
});
jobArray.forEach(job => {
queue = queue.then(() => collectVideosData(job));
});
queue.then(() => console.log("finish"));
// or if you are inside an async function
await queue;
console.log("finish");
即使由于承诺彼此等待,还是出于某种原因仍要在其中添加延迟(setTimeout
)。您可以通过将循环更改为以下方式来实现:
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
jobArray.forEach((job, i) => {
queue = queue.then(() => collectChannelsData(job))
.then(() => wait(10000 * i));
});
我个人将await
设为answered by jfriend00,进入清洁循环。
您可以通过定义诸如Queue
之类的帮助程序类来使上述内容更简洁,该类将执行很多重复的代码queue = queue.then(...)
。
class Queue {
constructor(initial) {
this.promise = Promise.resolve(initial);
}
then(onFulfilled, onRejected) {
return this.promise.then(onFulfilled, onRejected);
}
add(task) {
this.promise = this.then(task);
}
}
const queue = new Queue();
jobArray.forEach(job => queue.add(() => collectChannelsData(job)));
jobArray.forEach(job => queue.add(() => collectVideosData(job)));
await queue; // <- assuming you are in async function context
console.log("finish");