Como adiciono um atraso em um loop JavaScript?

345

Gostaria de adicionar um atraso / suspensão dentro de um whileloop:

Eu tentei assim:

alert('hi');

for(var start = 1; start < 10; start++) {
  setTimeout(function () {
    alert('hello');
  }, 3000);
}

Somente o primeiro cenário é verdadeiro: após a exibição alert('hi'), ele estará aguardando 3 segundos e alert('hello')será exibido, mas alert('hello')será repetidamente constantemente.

O que eu gostaria é que depois alert('hello')seja mostrado 3 segundos depois alert('hi'), ele precisará aguardar 3 segundos pela segunda vez alert('hello')e assim por diante.

olidev
fonte

Respostas:

749

A setTimeout()função é sem bloqueio e retornará imediatamente. Portanto, seu loop irá iterar muito rapidamente e iniciará o tempo limite de 3 segundos, disparando um após o outro em rápida sucessão. É por isso que seus primeiros alertas são exibidos após 3 segundos e todo o resto segue em sucessão sem demora.

Você pode usar algo assim:

var i = 1;                  //  set your counter to 1

function myLoop() {         //  create a loop function
  setTimeout(function() {   //  call a 3s setTimeout when the loop is called
    console.log('hello');   //  your code here
    i++;                    //  increment the counter
    if (i < 10) {           //  if the counter < 10, call the loop function
      myLoop();             //  ..  again which will trigger another 
    }                       //  ..  setTimeout()
  }, 3000)
}

myLoop();                   //  start the loop

Você também pode aprimorá-lo, usando uma função de auto-chamada, passando o número de iterações como argumento:

(function myLoop(i) {
  setTimeout(function() {
    console.log('hello'); //  your code here                
    if (--i) myLoop(i);   //  decrement i and call myLoop again if i > 0
  }, 3000)
})(10);                   //  pass the number of iterations as an argument

Daniel Vassallo
fonte
26
O uso da recursão para implementar isso não estaria sujeito a um estouro de pilha eventualmente? Se você quisesse fazer um milhão de iterações, qual seria a melhor maneira de implementar isso? Talvez setInterval e depois limpe-o, como a solução de Abel abaixo?
Adam
7
@ Adam: meu entendimento é que, como setTimeout é sem bloqueio, isso não é recusa - a janela de pilha fecha após cada setTimeout e há apenas um setTimeout esperando para executar ... Certo?
Joe
3
Como isso funcionaria ao iterar um objeto como um for inloop?
vsync
11
@vsync Look intoObject.keys()
Braden Best
11
@joey Você está confundindo setTimeoutcom setInterval. Os tempos limites são implicitamente destruídos quando o retorno de chamada é chamado.
Cdhowie # 01/18
72

Tente algo como isto:

var i = 0, howManyTimes = 10;
function f() {
    alert( "hi" );
    i++;
    if( i < howManyTimes ){
        setTimeout( f, 3000 );
    }
}
f();
cji
fonte
69

Se estiver usando o ES6, você poderá usar letisso para:

for (let i=1; i<10; i++) {
    setTimeout( function timer(){
        alert("hello world");
    }, i*3000 );
}

O que letfaz é declarar ipara cada iteração , não o loop. Dessa forma, o que é passado setTimeouté exatamente o que queremos.

Saket Mehta
fonte
11
Obrigado! Não teria pensado nesse método sozinho. Escopo real do bloco. Imagine que ...
Sophia ouro
11
Eu acredito que este tem os mesmos problemas de alocação de memória como a resposta descrito no stackoverflow.com/a/3583795/1337392
Flame_Phoenix
11
@Flame_Phoenix Que problemas de alocação de memória?
4castle
11
A chamada setTimeout calcula de forma síncrona o valor do i*3000argumento, dentro do loop, e o passa setTimeoutpor valor. O uso de leté opcional e não está relacionado ao questionário e à resposta.
traktor53
@Flame_Phoenix mencionou que há problemas neste código. Basicamente, na primeira passagem, você cria o timer e repete imediatamente o loop repetidamente até o final do loop pela condição ( i < 10), para que você tenha vários timers trabalhando em paralelo, o que cria alocação de memória e é pior em uma quantidade maior de iterações.
XCanG 17/07/19
63

Como o ES7 existe uma maneira melhor de aguardar um loop:

// Returns a Promise that resolves after "ms" Milliseconds
function timer(ms) {
 return new Promise(res => setTimeout(res, ms));
}

async function load () { // We need to wrap the loop into an async function for this to work
  for (var i = 0; i < 3; i++) {
    console.log(i);
    await timer(3000); // then the created Promise can be awaited
  }
}

load();

Quando o mecanismo atinge a awaitpeça, ele define um tempo limite e interrompe a execução doasync function . Então, quando o tempo limite terminar, a execução continuará nesse ponto. Isso é bastante útil, pois você pode atrasar (1) loops aninhados, (2) condicionalmente, (3) funções aninhadas:

async function task(i) { // 3
  await timer(1000);
  console.log(`Task ${i} done!`);
}

async function main() {
  for(let i = 0; i < 100; i+= 10) {
    for(let j = 0; j < 10; j++) { // 1
      if(j % 2) { // 2
        await task(i + j);
      }
    }
  }
}
    
main();

function timer(ms) { return new Promise(res => setTimeout(res, ms)); }

Referência no MDN

Embora o ES7 agora seja suportado pelo NodeJS e navegadores modernos, convém transpilar com o BabelJS para que ele seja executado em qualquer lugar.

Jonas Wilms
fonte
Isso funciona bem para mim. Eu só quero perguntar que, se eu quiser interromper o loop, como posso fazer isso ao usar wait?
Sachin Shah
@sachin break;talvez?
Jonas Wilms
Obrigado por esta solução. É bom usar todas as estruturas de controle existentes e não precisar inventar continuações.
Gus
Isso ainda criaria vários temporizadores e eles resolveriam em momentos diferentes, e não em sequência?
David Yell
@david um nope? Você realmente executou o código?
Jonas Wilms
24

Outra maneira é multiplicar o tempo limite, mas observe que isso não é como dormir . O código após o loop será executado imediatamente, apenas a execução da função de retorno de chamada é adiada.

for (var start = 1; start < 10; start++)
    setTimeout(function () { alert('hello');  }, 3000 * start);

O primeiro tempo limite será definido como 3000 * 1, o segundo 3000 * 2e assim por diante.

Felix Kling
fonte
2
Vale ressaltar que você não pode usar com segurança startdentro de sua função usando este método.
DBS
2
Má prática - alocação de memória desnecessária.
Alexander Trakhimenok
Voto positivo por criatividade, mas é uma prática muito ruim. :)
Salivan
2
Por que é uma prática ruim e por que tem problemas de alocação de memória? Esta resposta sofre os mesmos problemas? stackoverflow.com/a/36018502/1337392
Flame_Phoenix
11
@Flame_Phoenix é uma prática ruim porque o programa manterá um cronômetro para cada loop, com todos os cronômetros em execução ao mesmo tempo. Portanto, se houver 1000 iterações, haverá 1000 temporizadores sendo executados ao mesmo tempo no início.
Joakim
16

Isso vai funcionar

for (var i = 0; i < 10; i++) {
  (function(i) {
    setTimeout(function() { console.log(i); }, 100 * i);
  })(i);
}

Experimente este violino: https://jsfiddle.net/wgdx8zqq/

Gsvp Nagaraju
fonte
11
Isso faz desencadear todas as chamadas de tempo de espera perto do mesmo tempo, porém
Eddie
a única coisa que digo, eu quebrei dessa maneira, usei, $.Deferredmas foi um cenário diferente para deixar funcionou, polegar pra você ..!
ArifMustafa
15

Eu acho que você precisa de algo assim:

var TimedQueue = function(defaultDelay){
    this.queue = [];
    this.index = 0;
    this.defaultDelay = defaultDelay || 3000;
};

TimedQueue.prototype = {
    add: function(fn, delay){
        this.queue.push({
            fn: fn,
            delay: delay
        });
    },
    run: function(index){
        (index || index === 0) && (this.index = index);
        this.next();
    },
    next: function(){
        var self = this
        , i = this.index++
        , at = this.queue[i]
        , next = this.queue[this.index]
        if(!at) return;
        at.fn();
        next && setTimeout(function(){
            self.next();
        }, next.delay||this.defaultDelay);
    },
    reset: function(){
        this.index = 0;
    }
}

Código do teste:

var now = +new Date();

var x = new TimedQueue(2000);

x.add(function(){
    console.log('hey');
    console.log(+new Date() - now);
});
x.add(function(){
    console.log('ho');
    console.log(+new Date() - now);
}, 3000);
x.add(function(){
    console.log('bye');
    console.log(+new Date() - now);
});

x.run();

Nota: o uso de alertas interrompe a execução do javascript até você fechar o alerta. Pode ser mais código do que você solicitou, mas esta é uma solução reutilizável robusta.

BGerrissen
fonte
15

Eu provavelmente usaria setInteval. Como isso,

var period = 1000; // ms
var endTime = 10000;  // ms
var counter = 0;
var sleepyAlert = setInterval(function(){
    alert('Hello');
    if(counter === endTime){
       clearInterval(sleepyAlert);
    }
    counter += period;
}, period);
Abel Terefe
fonte
3
SetTimeout é muito melhor que settinterval. google-lo e você vai saber
Airy
14
Pesquisei um pouco no google e não encontrei nada. Por que setInterval é ruim? Você pode nos dar um link? ou um exemplo? Graças
Marcs
Eu acho que o ponto era que SetInterval()continua gerando 'threads', mesmo no caso de algum erro ou bloco.
Mateen Ulhaq
8

No ES6 (ECMAScript 2015), você pode iterar com atraso com gerador e intervalo.

Geradores, um novo recurso do ECMAScript 6, são funções que podem ser pausadas e retomadas. Chamar genFunc não o executa. Em vez disso, ele retorna o chamado objeto gerador que nos permite controlar a execução do genFunc. genFunc () é inicialmente suspenso no início do seu corpo. O método genObj.next () continua a execução de genFunc, até o próximo rendimento. (Explorando o ES6)


Exemplo de código:

let arr = [1, 2, 3, 'b'];
let genObj = genFunc();

let val = genObj.next();
console.log(val.value);

let interval = setInterval(() => {
  val = genObj.next();
  
  if (val.done) {
    clearInterval(interval);
  } else {
    console.log(val.value);
  }
}, 1000);

function* genFunc() {
  for(let item of arr) {
    yield item;
  }
}

Portanto, se você estiver usando o ES6, essa é a maneira mais elegante de obter um loop com atraso (na minha opinião).

Itay Radotzki
fonte
4

Você pode usar o operador de intervalo RxJS . O intervalo emite número inteiro a cada x número de segundos e take é especificar o número de vezes que ele deve emitir números

Rx.Observable
  .interval(1000)
  .take(10)
  .subscribe((x) => console.log(x))
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.1.0/rx.lite.min.js"></script>

Vlad Bezden
fonte
4

Só pensei em postar meus dois centavos aqui também. Esta função executa um loop iterativo com um atraso. Veja este jsfiddle . A função é a seguinte:

function timeout(range, time, callback){
    var i = range[0];                
    callback(i);
    Loop();
    function Loop(){
        setTimeout(function(){
            i++;
            if (i<range[1]){
                callback(i);
                Loop();
            }
        }, time*1000)
    } 
}

Por exemplo:

//This function prints the loop number every second
timeout([0, 5], 1, function(i){
    console.log(i);
});

Seria equivalente a:

//This function prints the loop number instantly
for (var i = 0; i<5; i++){
    console.log(i);
}
D Slee
fonte
4

Eu faço isso com Bluebird Promise.delaye recursão.

function myLoop(i) {
  return Promise.delay(1000)
    .then(function() {
      if (i > 0) {
        alert('hello');
        return myLoop(i -= 1);
      }
    });
}

myLoop(3);
<script src="//cdnjs.cloudflare.com/ajax/libs/bluebird/2.9.4/bluebird.min.js"></script>

Dave Bryand
fonte
2

No ES6, você pode fazer o seguinte:

 for (let i = 0; i <= 10; i++){       
     setTimeout(function () {   
        console.log(i);
     }, i*3000)
 }

No ES5, você pode fazer o seguinte:

for (var i = 0; i <= 10; i++){
   (function(i) {          
     setTimeout(function () {   
        console.log(i);
     }, i*3000)
   })(i);  
 }

O motivo é que letpermite declarar variáveis ​​limitadas ao escopo de uma instrução de bloco ou expressão na qual ela é usada, diferentemente da varpalavra - chave, que define uma variável globalmente ou localmente para uma função inteira, independentemente do escopo do bloco.

Tabish
fonte
1

Uma versão modificada da resposta de Daniel Vassallo, com variáveis ​​extraídas em parâmetros para tornar a função mais reutilizável:

Primeiro vamos definir algumas variáveis ​​essenciais:

var startIndex = 0;
var data = [1, 2, 3];
var timeout = 3000;

Em seguida, você deve definir a função que deseja executar. Isso será passado para i, o índice atual do loop e o comprimento do loop, caso você precise:

function functionToRun(i, length) {
    alert(data[i]);
}

Versão auto-executável

(function forWithDelay(i, length, fn, delay) {
   setTimeout(function () {
      fn(i, length);
      i++;
      if (i < length) {
         forWithDelay(i, length, fn, delay); 
      }
  }, delay);
})(startIndex, data.length, functionToRun, timeout);

Versão funcional

function forWithDelay(i, length, fn, delay) {
   setTimeout(function () {
      fn(i, length);
      i++;
      if (i < length) {
         forWithDelay(i, length, fn, delay); 
      }
  }, delay);
}

forWithDelay(startIndex, data.length, functionToRun, timeout); // Lets run it
Jasdeep Khalsa
fonte
nice one e como faço para passar dados para a função sem uma variável global
Sundara Prabu
1
   let counter =1;
   for(let item in items) {
        counter++;
        setTimeout(()=>{
          //your code
        },counter*5000); //5Sec delay between each iteration
    }
Ali Azhar
fonte
1

Faz você:

console.log('hi')
let start = 1
setTimeout(function(){
  let interval = setInterval(function(){
    if(start == 10) clearInterval(interval)
    start++
    console.log('hello')
  }, 3000)
}, 3000)
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

Nguyen Ba Danh - FAIC HN
fonte
melhores registos usar o console em vez de alertas, não foi muito divertido para fechar os alertas por meio minuto;)
Hendry
Sim. Entendo! Mas o pedido está alerta ... huz
Nguyen Ba Danh - FAIC HN
Por que importar o jQuery?
Elias Soares
Desculpe ... desnecessário .. heh. Não sei postar conteúdo ... isso primeiro.
Nguyen Ba Danh - FAIC HN
0
/* 
  Use Recursive  and setTimeout 
  call below function will run loop loopFunctionNeedCheck until 
  conditionCheckAfterRunFn = true, if conditionCheckAfterRunFn == false : delay 
  reRunAfterMs miliseconds and continue loop
  tested code, thanks
*/

function functionRepeatUntilConditionTrue(reRunAfterMs, conditionCheckAfterRunFn,
 loopFunctionNeedCheck) {
    loopFunctionNeedCheck();
    var result = conditionCheckAfterRunFn();
    //check after run
    if (!result) {
        setTimeout(function () {
            functionRepeatUntilConditionTrue(reRunAfterMs, conditionCheckAfterRunFn, loopFunctionNeedCheck)
        }, reRunAfterMs);
    }
    else  console.log("completed, thanks");    
            //if you need call a function after completed add code call callback in here
}

//passing-parameters-to-a-callback-function
// From Prototype.js 
if (!Function.prototype.bind) { // check if native implementation available
    Function.prototype.bind = function () {
        var fn = this, args = Array.prototype.slice.call(arguments),
            object = args.shift();
        return function () {
            return fn.apply(object,
              args.concat(Array.prototype.slice.call(arguments)));
        };
    };
}

//test code: 
var result = 0; 
console.log("---> init result is " + result);
var functionNeedRun = function (step) {           
   result+=step;    
       console.log("current result is " + result);  
}
var checkResultFunction = function () {
    return result==100;
}  

//call this function will run loop functionNeedRun and delay 500 miliseconds until result=100    
functionRepeatUntilConditionTrue(500, checkResultFunction , functionNeedRun.bind(null, 5));

//result log from console:
/*
---> init result is 0
current result is 5
undefined
current result is 10
current result is 15
current result is 20
current result is 25
current result is 30
current result is 35
current result is 40
current result is 45
current result is 50
current result is 55
current result is 60
current result is 65
current result is 70
current result is 75
current result is 80
current result is 85
current result is 90
current result is 95
current result is 100
completed, thanks
*/
user2913925
fonte
7
Os nomes das suas funções são horríveis, é a principal razão pela qual esse código é tão difícil de ler.
Mark Walters
0

Aqui está como eu criei um loop infinito com um atraso que quebra em uma determinada condição:

  // Now continuously check the app status until it's completed, 
  // failed or times out. The isFinished() will throw exception if
  // there is a failure.
  while (true) {
    let status = await this.api.getStatus(appId);
    if (isFinished(status)) {
      break;
    } else {
      // Delay before running the next loop iteration:
      await new Promise(resolve => setTimeout(resolve, 3000));
    }
  }

A chave aqui é criar uma nova promessa que resolve por tempo limite e aguardar sua resolução.

Obviamente, você precisa de suporte assíncrono / aguardado para isso. Funciona no nó 8.


fonte
0

para uso comum "esqueça os loops normais" e use essa combinação de "setInterval" que inclui "setTimeOut" s: assim (das minhas tarefas reais).

        function iAsk(lvl){
            var i=0;
            var intr =setInterval(function(){ // start the loop 
                i++; // increment it
                if(i>lvl){ // check if the end round reached.
                    clearInterval(intr);
                    return;
                }
                setTimeout(function(){
                    $(".imag").prop("src",pPng); // do first bla bla bla after 50 millisecond
                },50);
                setTimeout(function(){
                     // do another bla bla bla after 100 millisecond.
                    seq[i-1]=(Math.ceil(Math.random()*4)).toString();
                    $("#hh").after('<br>'+i + ' : rand= '+(Math.ceil(Math.random()*4)).toString()+' > '+seq[i-1]);
                    $("#d"+seq[i-1]).prop("src",pGif);
                    var d =document.getElementById('aud');
                    d.play();                   
                },100);
                setTimeout(function(){
                    // keep adding bla bla bla till you done :)
                    $("#d"+seq[i-1]).prop("src",pPng);
                },900);
            },1000); // loop waiting time must be >= 900 (biggest timeOut for inside actions)
        }

PS: Entenda que o comportamento real de (setTimeOut): todos eles começarão no mesmo tempo "os três bla bla bla começarão a contagem regressiva no mesmo momento", então faça um tempo limite diferente para organizar a execução.

PS 2: o exemplo do loop de temporização, mas para os loops de reação, você pode usar eventos, prometer assíncrono aguardar.

Mohamed Abulnasr
fonte
0

<!DOCTYPE html>
<html>
<body>

<button onclick="myFunction()">Try it</button>

<p id="demo"></p>

<script>
function myFunction() {
    for(var i=0; i<5; i++) {
    	var sno = i+1;
       	(function myLoop (i) {          
             setTimeout(function () {   
             	alert(i); // Do your function here 
             }, 1000*i);
        })(sno);
    }
}
</script>

</body>
</html>

Boginaathan M
fonte
11
Sempre forneça pelo menos uma breve descrição aos seus trechos de código, pelo menos para que outras pessoas tenham certeza de que você abordou a questão.
Hexfire 23/01
11
Código únicas respostas Arent incentivado como eles não fornecem muita informação para futuros leitores, fornecer uma explicação para o que você escreveu
WhatsThePoint
0

Que eu saiba, a setTimeoutfunção é chamada de forma assíncrona. O que você pode fazer é agrupar todo o loop em uma função assíncrona e aguardar um Promiseque contenha o setTimeout, como mostrado:

var looper = async function () {
  for (var start = 1; start < 10; start++) {
    await new Promise(function (resolve, reject) {
      setTimeout(function () {
        console.log("iteration: " + start.toString());
        resolve(true);
      }, 1000);
    });
  }
  return true;
}

E então você chama de executá-lo assim:

looper().then(function(){
  console.log("DONE!")
});

Reserve um tempo para entender bem a programação assíncrona.

Questionare232
fonte
0

Apenas tente isso

 var arr = ['A','B','C'];
 (function customLoop (arr, i) {
    setTimeout(function () {
    // Do here what you want to do.......
    console.log(arr[i]);
    if (--i) {                
      customLoop(arr, i); 
    }
  }, 2000);
})(arr, arr.length);

Resultado

A // after 2s
B // after 2s
C // after 2s
Shirantha Madusanka
fonte
-1

Aqui está uma função que eu uso para fazer loop sobre uma matriz:

function loopOnArrayWithDelay(theArray, delayAmount, i, theFunction, onComplete){

    if (i < theArray.length && typeof delayAmount == 'number'){

        console.log("i "+i);

        theFunction(theArray[i], i);

        setTimeout(function(){

            loopOnArrayWithDelay(theArray, delayAmount, (i+1), theFunction, onComplete)}, delayAmount);
    }else{

        onComplete(i);
    }
}

Você o usa assim:

loopOnArrayWithDelay(YourArray, 1000, 0, function(e, i){
    //Do something with item
}, function(i){
    //Do something once loop has completed
}
PJeremyMalouf
fonte
-1

Este script funciona para a maioria das coisas

function timer(start) {
    setTimeout(function () { //The timer
        alert('hello');
    }, start*3000); //needs the "start*" or else all the timers will run at 3000ms
}

for(var start = 1; start < 10; start++) {
    timer(start);
}
Jaketr00
fonte
-1

Tente isso ...

var icount=0;
for (let i in items) {
   icount=icount+1000;
   new beginCount(items[i],icount);
}

function beginCount(item,icount){
  setTimeout(function () {

   new actualFunction(item,icount);

 }, icount);
}

function actualFunction(item,icount){
  //...runs ever 1 second
 console.log(icount);
}
PHILL BOOTH
fonte
-1

Implementação simples de mostrar um pedaço de texto a cada dois segundos, enquanto o loop estiver em execução.

for (var i = 0; i < foo.length; i++) {
   setInterval(function(){ 
     console.log("I will appear every 2 seconds"); 
   }, 2000);
  break;
};
squeekyDave
fonte
-3

Tente isto

//the code will execute in 1 3 5 7 9 seconds later
function exec(){
  for(var i=0;i<5;i++){
   setTimeout(function(){
     console.log(new Date());   //It's you code
   },(i+i+1)*1000);
  }
}
Steve Jiang
fonte