使用 forEach 循环执行每次迭代后添加延迟
Posted
技术标签:
【中文标题】使用 forEach 循环执行每次迭代后添加延迟【英文标题】:Add a delay after executing each iteration with forEach loop 【发布时间】:2018-01-11 22:11:00 【问题描述】:有没有一种简单的方法可以减慢 forEach 中的迭代(使用纯 javascript)?例如:
var items = document.querySelector('.item');
items.forEach(function(el)
// do stuff with el and pause before the next el;
);
【问题讨论】:
***.com/questions/1447407/… 谢谢。我实际上是在尝试用普通的 javascript 来做到这一点。我编辑了我的问题。 【参考方案1】:Array#forEach
完全可以实现您想要实现的目标——尽管您可能会以不同的方式思考它。你可以不做这样的事情:
var array = ['some', 'array', 'containing', 'words'];
array.forEach(function (el)
console.log(el);
wait(1000); // wait 1000 milliseconds
);
console.log('Loop finished.');
... 并获得输出:
some
array // one second later
containing // two seconds later
words // three seconds later
Loop finished. // four seconds later
JavaScript 中没有同步的 wait
或 sleep
函数会阻止其后的所有代码。
在 JavaScript 中延迟某些东西的唯一方法是采用非阻塞方式。这意味着使用setTimeout
或其亲属之一。我们可以使用传递给Array#forEach
的函数的第二个参数:它包含当前元素的索引:
var array = ['some', 'array', 'containing', 'words'];
var interval = 1000; // how much time should the delay between two iterations be (in milliseconds)?
array.forEach(function (el, index)
setTimeout(function ()
console.log(el);
, index * interval);
);
console.log('Loop finished.');
使用index
,我们可以计算何时应该执行该函数。但是现在我们有一个不同的问题:console.log('Loop finished.')
在循环的第一次迭代之前执行。那是因为setTimout
是非阻塞的。
JavaScript 在循环中设置超时,但它不会等待超时完成。它只是在forEach
之后继续执行代码。
为了解决这个问题,我们可以使用Promise
s。让我们构建一个承诺链:
var array = ['some', 'array', 'containing', 'words'];
var interval = 1000; // how much time should the delay between two iterations be (in milliseconds)?
var promise = Promise.resolve();
array.forEach(function (el)
promise = promise.then(function ()
console.log(el);
return new Promise(function (resolve)
setTimeout(resolve, interval);
);
);
);
promise.then(function ()
console.log('Loop finished.');
);
有一篇关于Promise
s 和forEach
/map
/filter
here 的优秀文章。
如果数组可以动态更改,我会变得更棘手。在那种情况下,我认为不应该使用Array#forEach
。试试这个:
var array = ['some', 'array', 'containing', 'words'];
var interval = 2000; // how much time should the delay between two iterations be (in milliseconds)?
var loop = function ()
return new Promise(function (outerResolve)
var promise = Promise.resolve();
var i = 0;
var next = function ()
var el = array[i];
// your code here
console.log(el);
if (++i < array.length)
promise = promise.then(function ()
return new Promise(function (resolve)
setTimeout(function ()
resolve();
next();
, interval);
);
);
else
setTimeout(outerResolve, interval);
// or just call outerResolve() if you don't want to wait after the last element
;
next();
);
;
loop().then(function ()
console.log('Loop finished.');
);
var input = document.querySelector('input');
document.querySelector('button').addEventListener('click', function ()
// add the new item to the array
array.push(input.value);
input.value = '';
);
<input type="text">
<button>Add to array</button>
【讨论】:
这很好用。现在,假设服务器在 promise 循环运行时向数组添加元素。是否有一种简单的方法 - 在循环内 - 查询和附加数组?假设它是 var array = document.querySelectorAll('.all-at-the-moment-but-stay-tuned'); 可能,但更复杂。 不应该执行通过函数的解析吗? : setTimeout(resolve(), 间隔);而不是:setTimeout(resolve, interval); 仍然有效,能够减慢调用客户端 api 的 foreach 的速度,该客户端 api 抱怨调用的速度有多快。【参考方案2】:您需要使用 setTimeout 来创建延迟并进行递归实现
你的例子应该是这样的
var items = ['a', 'b', 'c']
var i = 0;
(function loopIt(i)
setTimeout(function()
// your code handling here
console.log(items[i]);
if(i < items.length - 1) loopIt(i+1)
, 2000);
)(i)
【讨论】:
这只是并行初始化所有超时。它们或多或少会同时执行。此外,内部闭包没有任何作用。 @DavidL.Walsh 我的错误,你是对的。我编辑了我的答案【参考方案3】:我认为递归提供了最简单的解决方案。
function slowIterate(arr)
if (arr.length === 0)
return;
console.log(arr[0]); // <-- replace with your custom code
setTimeout(() =>
slowIterate(arr.slice(1));
, 1000); // <-- replace with your desired delay (in milliseconds)
slowIterate(Array.from(document.querySelector('.item')));
【讨论】:
【参考方案4】:您可以使用async/await
、Promise
构造函数、setTimeout()
和for..of
循环按顺序执行任务,其中可以在执行任务之前设置duration
(async() =>
const items = [
prop: "a",
delay: Math.floor(Math.random() * 1001)
,
prop: "b",
delay: 2500
,
prop: "c",
delay: 1200
];
const fx = (prop, delay) =>
new Promise(resolve => setTimeout(resolve, delay, prop)) // delay
.then(data => console.log(data)) // do stuff
for (let prop, delay of items)
// do stuff with el and pause before the next el;
let curr = await fx(prop, delay);
;
)();
【讨论】:
@KirkRoss 注意,//delay
,// do stuff
at code 的顺序可以调整为//do stuff
,// delay
【参考方案5】:
使用 JS Promises 和 asnyc/await
语法,您可以创建真正有效的 sleep
函数。但是,forEach
会同步调用每个迭代,因此您会得到 1 秒的延迟,然后是一次所有项目。
const items = ["abc", "def", "ghi", "jkl"];
const sleep = (ms) => new Promise((res) => setTimeout(res, ms));
items.forEach(async (item) =>
await sleep(1000);
console.log(item);
);
我们可以改为使用setInterval
和clearInterval
(或setTimeout
,但我们使用前者)来创建一个定时forEach 循环,如下所示:
function forEachWithDelay(array, callback, delay)
let i = 0;
let interval = setInterval(() =>
callback(array[i], i, array);
if (++i === array.length) clearInterval(interval);
, delay);
const items = ["abc", "def", "ghi", "jkl"];
forEachWithDelay(items, (item, i) => console.log(`#$i: $item`), 1000);
【讨论】:
【参考方案6】:首先你必须改变你的代码:
var items = document.querySelectorAll('.item'), i;
for (i = 0; i < items.length; ++i)
// items[i] <--- your element
你可以在 JavaScript 中使用 forEach 轻松循环数组,但是 不幸的是,结果并不是那么简单 querySelectorAll
阅读更多关于它的信息here
我可以建议您阅读此answer 以找到正确的睡眠解决方案
【讨论】:
【参考方案7】:你可以做出一个promise并用for来使用它,这个例子必须在一个async/await函数中:
let myPromise = () => new Promise((resolve, reject) =>
setTimeout(function()
resolve('Count')
, 1000)
)
for (let index = 0; index < 100; index++)
let count = await myPromise()
console.log(`$count: $index`)
【讨论】:
【参考方案8】:Generators
function* elGenLoop (els)
let count = 0;
while (count < els.length)
yield els[count++];
// This will also work with a NodeList
// Such as `const elList = elGenLoop(document.querySelector('.item'));`
const elList = elGenLoop(['one', 'two', 'three']);
console.log(elList.next().value); // one
console.log(elList.next().value); // two
console.log(elList.next().value); // three
这使您可以完全控制何时要访问列表中的下一个迭代。
【讨论】:
以上是关于使用 forEach 循环执行每次迭代后添加延迟的主要内容,如果未能解决你的问题,请参考以下文章
C# 使用IEnumerable,yield 返回结果,同时使用foreach时,在循环内修改变量的值无效