【问题标题】:Add a delay after executing each iteration with forEach loop使用 forEach 循环执行每次迭代后添加延迟
【发布时间】: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;
});

【问题讨论】:

标签: javascript loops foreach delay pause


【解决方案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 中没有同步的 waitsleep 函数会阻止其后的所有代码。

在 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之后继续执行代码。

为了解决这个问题,我们可以使用Promises。让我们构建一个承诺链:

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.');
});

有一篇关于Promises 和forEach/map/filterhere 的优秀文章。


如果数组可以动态更改,我会变得更棘手。在那种情况下,我认为不应该使用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/awaitPromise 构造函数、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);
    });

    我们可以改为使用setIntervalclearInterval(或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
          

          这使您可以完全控制何时要访问列表中的下一个迭代。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2015-01-14
            • 2016-04-09
            • 1970-01-01
            • 2013-10-11
            相关资源
            最近更新 更多