Comment ajouter un retard dans une boucle JavaScript?

346

Je voudrais ajouter un délai / sommeil à l'intérieur d'une whileboucle:

Je l'ai essayé comme ça:

alert('hi');

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

Seul le premier scénario est vrai: après affichage alert('hi'), il attendra 3 secondes puis alert('hello')s'affichera mais alert('hello')se répétera constamment.

Ce que je voudrais, c'est qu'après alert('hello')s'affiche 3 secondes après, alert('hi')il doit attendre 3 secondes pour la deuxième fois alert('hello')et ainsi de suite.

olidev
la source

Réponses:

750

La setTimeout()fonction est non bloquante et reviendra immédiatement. Par conséquent, votre boucle itérera très rapidement et déclenchera successivement des déclencheurs de temporisation de 3 secondes. C'est pourquoi vos premières alertes apparaissent après 3 secondes, et tout le reste se succède sans délai.

Vous voudrez peut-être utiliser quelque chose comme ceci à la place:

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

Vous pouvez également le nettoyer, en utilisant une fonction auto-invoquante, en passant le nombre d'itérations comme argument:

(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
la source
27
L'utilisation de la récursivité pour implémenter cela ne serait-elle pas éventuellement sujette à un débordement de pile? Si vous vouliez faire un million d'itérations, quelle serait la meilleure façon de mettre cela en œuvre? Peut-être setInterval puis effacez-le, comme la solution d'Abel ci-dessous?
Adam
7
@Adam: ma compréhension est que, puisque setTimeout n'est pas bloquant, ce n'est pas une récusion - la pile de fenêtres se ferme après chaque setTimeout et il n'y a qu'un seul setTimeout en attente d'exécution ... N'est-ce pas?
Joe
3
Comment cela fonctionnerait-il lors de l'itération d'un objet comme une for inboucle?
vsync
1
@vsync Look intoObject.keys()
Braden Best
1
@joey Vous confondez setTimeoutavec setInterval. Les délais d'attente sont implicitement détruits lorsque le rappel est appelé.
cdhowie
72

Essayez quelque chose comme ceci:

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

Si vous utilisez ES6, vous pouvez utiliser letpour y parvenir:

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

Qu'est let- ce que c'est déclarer ipour chaque itération , pas la boucle. De cette façon, ce qui est transmis setTimeoutest exactement ce que nous voulons.

Saket Mehta
la source
1
Remercier! N'aurais pas pensé à cette méthode par moi-même. Portée réelle du bloc. Imaginez que ...
Sophia Gold
1
Je crois que cela a les mêmes problèmes d'allocation de mémoire que la réponse décrite dans stackoverflow.com/a/3583795/1337392
Flame_Phoenix
1
@Flame_Phoenix Quels problèmes d'allocation de mémoire?
4castle
1
L'appel setTimeout calcule de manière synchrone la valeur de l' i*3000argument, à l'intérieur de la boucle, et la transmet à setTimeoutpar valeur. L'utilisation de letest facultative et sans rapport avec la question et la réponse.
traktor53
@Flame_Phoenix a mentionné qu'il y avait des problèmes dans ce code. Fondamentalement, lors de la première passe, vous créez un minuteur, puis répétez immédiatement la boucle encore et encore jusqu'à la fin de la boucle par condition ( i < 10), de sorte que plusieurs minuteries fonctionnent en parallèle, ce qui crée une allocation de mémoire et c'est pire sur une plus grande quantité d'itérations.
XCanG
63

Depuis ES7 il y a une meilleure façon d' attendre une boucle:

// 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();

Lorsque le moteur atteint la awaitpièce, il définit un délai d'attente et arrête l'exécution de laasync function . Ensuite, lorsque le délai est écoulé, l'exécution se poursuit à ce stade. C'est très utile car vous pouvez retarder (1) les boucles imbriquées, (2) conditionnellement, (3) les fonctions imbriquées:

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

Référence sur MDN

Alors que ES7 est désormais pris en charge par NodeJS et les navigateurs modernes, vous voudrez peut-être le transpiler avec BabelJS pour qu'il fonctionne partout.

Jonas Wilms
la source
Ça fonctionne bien pour moi. Je veux simplement demander que si je veux rompre la boucle, comment puis-je le faire lorsque j'attends?
Sachin Shah
@sachin break;peut-être?
Jonas Wilms
Merci pour cette solution. Il est agréable d'utiliser toutes les structures de contrôle existantes et de ne pas avoir besoin d'inventer des suites.
Gus
Cela ne ferait que créer différents temporisateurs et ils se résoudraient à des moments différents plutôt qu'en séquence?
David Yell,
@JonasWilms Il me semble que j'ai totalement raté le bouton "Exécuter l'extrait": facepalm:
David Yell
24

Une autre façon consiste à multiplier le délai d'expiration, mais notez que ce n'est pas comme le sommeil . Le code après la boucle sera exécuté immédiatement, seule l'exécution de la fonction de rappel est différée.

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

La première temporisation sera définie sur 3000 * 1, la seconde sur 3000 * 2et ainsi de suite.

Felix Kling
la source
2
Il convient de souligner que vous ne pouvez pas utiliser de manière fiable à l' startintérieur de votre fonction en utilisant cette méthode.
DBS
2
Mauvaise pratique - allocation de mémoire inutile.
Alexander Trakhimenok
Votez pour la créativité, mais c'est sacrément une mauvaise pratique. :)
Salivan
2
Pourquoi est-ce une mauvaise pratique et pourquoi a-t-il des problèmes d'allocation de mémoire? Cette réponse souffre-t-elle des mêmes problèmes? stackoverflow.com/a/36018502/1337392
Flame_Phoenix
1
@Flame_Phoenix c'est une mauvaise pratique parce que le programme gardera un temporisateur pour chaque boucle, avec tous les temporisateurs fonctionnant en même temps. Donc, s'il y a 1000 itérations, il y aura 1000 temporisateurs fonctionnant en même temps au début.
Joakim
16

Cela fonctionnera

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

Essayez ce violon: https://jsfiddle.net/wgdx8zqq/

Gsvp Nagaraju
la source
1
Cela déclenche cependant tous les appels de délai d'expiration presque en même temps
Eddie
la seule chose que je dis, j'ai craqué de cette façon, utilisé $.Deferredmais c'était un scénario différent pour le laisser fonctionner, bravo ..!
ArifMustafa
15

Je pense que vous avez besoin de quelque chose comme ça:

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;
    }
}

Code de test:

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();

Remarque: l'utilisation d'alertes bloque l'exécution de javascript jusqu'à la fermeture de l'alerte. Il peut s'agir de plus de code que vous n'en avez demandé, mais il s'agit d'une solution robuste et réutilisable.

BGerrissen
la source
15

J'utiliserais probablement setInteval. Comme ça,

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
la source
3
SetTimeout est bien meilleur que settinterval. google it et vous saurez
Airy
14
Je le google un peu et je n'ai rien trouvé, pourquoi setInterval est mauvais? Pouvez-vous nous donner un lien? ou un exemple? Merci
Marcs
J'imagine que le fait était que la SetInterval()génération de «threads» continue même en cas d'erreur ou de blocage.
Mateen Ulhaq
8

Dans ES6 (ECMAScript 2015), vous pouvez itérer avec retard avec générateur et intervalle.

Les générateurs, une nouvelle fonctionnalité d'ECMAScript 6, sont des fonctions qui peuvent être suspendues et reprises. L'appel de genFunc ne l'exécute pas. Au lieu de cela, il retourne un soi-disant objet générateur qui nous permet de contrôler l'exécution de genFunc. genFunc () est initialement suspendu au début de son corps. La méthode genObj.next () continue l'exécution de genFunc, jusqu'au prochain rendement. (Explorer ES6)


Exemple de code:

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;
  }
}

Donc, si vous utilisez ES6, c'est la façon la plus élégante de réaliser une boucle avec retard (à mon avis).

Itay Radotzki
la source
4

Vous pouvez utiliser l' opérateur d'intervalle RxJS . L'intervalle émet un entier toutes les x nombre de secondes, et prend est spécifier le nombre de fois où il doit émettre des nombres

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
la source
4

Je pensais juste publier mes deux cents ici aussi. Cette fonction exécute une boucle itérative avec un retard. Voir ce jsfiddle . La fonction est la suivante:

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)
    } 
}

Par exemple:

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

Serait équivalent à:

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

Je fais cela avec Bluebird's Promise.delayet récursivité.

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
la source
2

Dans ES6, vous pouvez procéder comme suit:

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

Dans ES5, vous pouvez faire comme:

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

La raison en est, letvous permet de déclarer des variables qui sont limitées à la portée d'une instruction de bloc, ou une expression sur laquelle elle est utilisée, contrairement au varmot clé, qui définit une variable globalement, ou localement à une fonction entière quelle que soit la portée du bloc.

Tabish
la source
1

Une version modifiée de la réponse de Daniel Vassallo, avec des variables extraites en paramètres pour rendre la fonction plus réutilisable:

Définissons d'abord quelques variables essentielles:

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

Ensuite, vous devez définir la fonction que vous souhaitez exécuter. Cela passera i, l'index actuel de la boucle et la longueur de la boucle, au cas où vous en auriez besoin:

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

Version auto-exécutable

(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);

Version fonctionnelle

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
la source
sympa et comment passer des données à la fonction sans variable globale
Sundara Prabu
1
   let counter =1;
   for(let item in items) {
        counter++;
        setTimeout(()=>{
          //your code
        },counter*5000); //5Sec delay between each iteration
    }
Ali Azhar
la source
1

Tu le fais:

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
la source
mieux utiliser les journaux de la console au lieu des alertes, n'était pas très amusant de fermer les alertes pendant une demi-minute;)
Hendry
Ouais. Je vois! Mais la demande est alerte ... huz
Nguyen Ba Danh - FAIC HN
Pourquoi importer jQuery?
Elias Soares
Désolé ... c'est inutile .. heh. Je ne sais pas le contenu du post ... ce premier.
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
la source
7
Les noms de vos fonctions sont horribles, c'est la principale raison pour laquelle ce code est si difficile à lire.
Mark Walters
0

Voici comment j'ai créé une boucle infinie avec un retard qui se casse à une certaine condition:

  // 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));
    }
  }

La clé ici est de créer une nouvelle promesse qui se résout par timeout, et d'attendre sa résolution.

Évidemment, vous avez besoin d'un support asynchrone / wait pour cela. Fonctionne dans le nœud 8.


la source
0

pour une utilisation courante "oubliez les boucles normales" et utilisez cette combinaison de "setInterval", y compris "setTimeOut": comme ceci (de mes tâches réelles).

        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: Comprenez que le comportement réel de (setTimeOut): ils commenceront tous en même temps "les trois bla bla bla commenceront à décompter au même moment" alors faites un timeout différent pour organiser l'exécution.

PS 2: l'exemple de la boucle de synchronisation, mais pour une boucle de réaction, vous pouvez utiliser des événements, promettre une asynchrone en attente.

Mohamed Abulnasr
la source
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
la source
1
Veuillez toujours fournir au moins une brève description de vos extraits de code, au moins pour que les autres s'assurent que vous répondez à la question.
Hexfire
1
Les réponses au code ne sont pas encouragées car elles ne fournissent pas beaucoup d'informations aux futurs lecteurs. Veuillez fournir des explications sur ce que vous avez écrit
WhatsThePoint
0

À ma connaissance, la setTimeoutfonction est appelée de manière asynchrone. Ce que vous pouvez faire est d'enrouler la boucle entière dans une fonction asynchrone et d'attendre un Promisequi contient le setTimeout comme indiqué:

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;
}

Et puis vous appelez l'exécuter comme suit:

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

Veuillez prendre un peu de temps pour bien comprendre la programmation asynchrone.

Questionare232
la source
0

Essayez ceci

 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);

Résultat

A // after 2s
B // after 2s
C // after 2s
Shirantha Madusanka
la source
-1

Voici une fonction que j'utilise pour boucler sur un tableau:

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

Vous l'utilisez comme ceci:

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

Ce script fonctionne pour la plupart des choses

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
la source
-1

Essaye ça...

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);
}
CABINE PHILL
la source
-1

Implémentation simple de l'affichage d'un morceau de texte toutes les deux secondes tant que la boucle est en cours d'exécution.

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

Essaye ça

//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
la source