web-dev-qa-db-fra.com

Court-circuit Array.pour chaque appel comme pause

[1,2,3].forEach(function(el) {
    if(el === 1) break;
});

Comment puis-je faire cela en utilisant la nouvelle méthode forEach en JavaScript? J'ai essayé return;, return false; et break. break plante et return ne fait que continuer l'itération.

1333
Scott Klarenbach

Il n'y a pas de capacité intégrée à break dans forEach. Pour interrompre l'exécution, il faudrait lancer une exception. par exemple.

_var BreakException = {};

try {
  [1, 2, 3].forEach(function(el) {
    console.log(el);
    if (el === 2) throw BreakException;
  });
} catch (e) {
  if (e !== BreakException) throw e;
}_

Les exceptions JavaScript ne sont pas très jolies. Une boucle for traditionnelle peut être plus appropriée si vous avez vraiment besoin de break à l'intérieur.

Utilisez Array#some

À la place, utilisez Array#some :

_[1, 2, 3].some(function(el) {
  console.log(el);
  return el === 2;
});_

Cela fonctionne parce que some renvoie true dès que l'un des rappels, exécuté dans l'ordre du tableau, renvoie true, court-circuitant l'exécution du reste.

some, son inverse every (qui s'arrêtera sur un _return false_), et forEach sont toutes des méthodes ECMAScript Fifth Edition qui devront être ajoutées à _Array.prototype_ sur les navigateurs où ils manquent.

1850
bobince

Il existe maintenant un moyen encore meilleur de le faire dans ECMAScript2015 (alias ES6) en utilisant le nouveau for of loop . Par exemple, ce code n'imprime pas les éléments du tableau après le nombre 5:

let arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
for (let el of arr) {
  console.log(el);
  if (el === 5) {
    break;
  }
}

De la docs:

pour ... dans et pour ... sur instructions: itérer sur quelque chose. La principale différence entre eux réside dans ce sur quoi ils reviennent. L'instruction for ... in effectue une itération sur les propriétés énumérables d'un objet, dans l'ordre d'insertion d'origine. L'instruction pour ... de effectue une itération sur les données définies par l'objet itérable.

Besoin de l'index dans l'itération? Vous pouvez utiliser Array.entries() :

for (const [index, el] of arr.entries()) {
  if ( index === 5 ) break;
}
323
canac

Vous pouvez utiliser la méthode every :

[1,2,3].every(function(el) {
    return !(el === 1);
});

ES6

[1,2,3].every( el => el !== 1 )

pour les anciens navigateurs, utilisez:

if (!Array.prototype.every)
{
  Array.prototype.every = function(fun /*, thisp*/)
  {
    var len = this.length;
    if (typeof fun != "function")
      throw new TypeError();

    var thisp = arguments[1];
    for (var i = 0; i < len; i++)
    {
      if (i in this &&
          !fun.call(thisp, this[i], i, this))
        return false;
    }

    return true;
  };
}

plus de détails ici .

187

Citant le documentation MDN de Array.prototype.forEach() :

Il n'y a aucun moyen d'arrêter ou de casser une boucle forEach() autrement qu'en lançant une exception. Si vous avez besoin d'un tel comportement, la méthode .forEach() est le outil incorrect , utilisez plutôt une boucle simple. Si vous testez les éléments du tableau pour un prédicat et avez besoin d'une valeur de retour booléenne, vous pouvez utiliser every() ou some() .

Pour votre code (dans la question), comme suggéré par @bobince, utilisez Array.prototype.some() . Cela convient très bien à votre cas d'utilisation.

Array.prototype.some() exécute la fonction de rappel une fois pour chaque élément présent dans le tableau jusqu'à ce qu'il en trouve un où le rappel renvoie une valeur de vérité (une valeur qui devient vraie lorsqu'elle est convertie en Boolean). Si un tel élément est trouvé, some() renvoie immédiatement la valeur true. Sinon, some() renvoie false. callback n'est appelé que pour les index du tableau auxquels des valeurs ont été assignées; il n'est pas appelé pour les index qui ont été supprimés ou auxquels aucune valeur n'a été attribuée.

64
Rahul Desai

Malheureusement, dans ce cas, ce sera beaucoup mieux si vous n'utilisez pas forEach. Utilisez plutôt une boucle normale for et elle fonctionnera désormais exactement comme vous le souhaiteriez.

var array = [1, 2, 3];
for (var i = 0; i < array.length; i++) {
  if (array[i] === 1){
    break;
  }
}
62
Weston Ganger

Pensez à utiliser la méthode jquery de each, car elle permet de renvoyer une fonction de rappel fausse à l'intérieur:

$.each(function(e, i) { 
   if (i % 2) return false;
   console.log(e)
})

Les bibliothèques Lodash fournissent également la méthode takeWhile pouvant être chaînée avec map/réduire/fold, etc.:

var users = [
  { 'user': 'barney',  'active': false },
  { 'user': 'fred',    'active': false },
  { 'user': 'pebbles', 'active': true }
];

_.takeWhile(users, function(o) { return !o.active; });
// => objects for ['barney', 'fred']

// The `_.matches` iteratee shorthand.
_.takeWhile(users, { 'user': 'barney', 'active': false });
// => objects for ['barney']

// The `_.matchesProperty` iteratee shorthand.
_.takeWhile(users, ['active', false]);
// => objects for ['barney', 'fred']

// The `_.property` iteratee shorthand.
_.takeWhile(users, 'active');
// => []
25
vittore

Dans votre exemple de code, il apparaît que Array.prototype.find est ce que vous recherchez: Array.prototype.find () et Array.prototype.findIndex ()

[1, 2, 3].find(function(el) {
    return el === 2;
}); // returns 2
18
Oliver Moran

Si vous souhaitez utiliser suggestion de Dean Edward et lancer l'erreur StopIteration pour sortir de la boucle sans avoir à l'attraper, vous pouvez utiliser la fonction suivante ( originellement à partir d'ici ):

// Use a closure to prevent the global namespace from be polluted.
(function() {
  // Define StopIteration as part of the global scope if it
  // isn't already defined.
  if(typeof StopIteration == "undefined") {
    StopIteration = new Error("StopIteration");
  }

  // The original version of Array.prototype.forEach.
  var oldForEach = Array.prototype.forEach;

  // If forEach actually exists, define forEach so you can
  // break out of it by throwing StopIteration.  Allow
  // other errors will be thrown as normal.
  if(oldForEach) {
    Array.prototype.forEach = function() {
      try {
        oldForEach.apply(this, [].slice.call(arguments, 0));
      }
      catch(e) {
        if(e !== StopIteration) {
          throw e;
        }
      }
    };
  }
})();

Le code ci-dessus vous permettra d'exécuter du code tel que celui-ci sans avoir à créer vos propres clauses try-catch:

// Show the contents until you get to "2".
[0,1,2,3,4].forEach(function(val) {
  if(val == 2)
    throw StopIteration;
  alert(val);
});

Une chose importante à retenir est que cela ne mettra à jour la fonction Array.prototype.forEach que si elle existe déjà. S'il n'existe pas déjà, cela ne le modifiera pas.

14
Chris West

Réponse courte: utilisez for...break pour cela ou modifiez votre code pour éviter la rupture de forEach. N'utilisez pas .some() ou .every() pour émuler for...break. Réécrivez votre code pour éviter la boucle for...break ou utilisez for...break. Chaque fois que vous utilisez ces méthodes comme alternative à for...break, Dieu tue le chaton.

Longue réponse:

.some() et .every() retournent tous deux boolean value, .some() renvoie true s'il existe un élément pour lequel la fonction transmise retourne true, chaque retourne false s'il existe un élément pour lequel la fonction transmise est renvoyée false. C'est ce que cela signifie. Utiliser des fonctions pour ce qu'elles ne veulent pas dire est bien pire que d'utiliser des tableaux pour la présentation au lieu de CSS, car cela frustre tout le monde qui lit votre code.

En outre, le seul moyen possible d'utiliser ces méthodes comme alternative à for...break consiste à créer des effets secondaires (modifier certains vars en dehors de la fonction de rappel .some()), ce qui n'est pas très différent de for...break.

Donc, utiliser .some() ou .every() comme alternative à la boucle for...break n'est pas exempt d'effets secondaires, ce n'est pas beaucoup plus propre que for...break, c'est frustrant, donc ce n'est pas mieux.

Vous pouvez toujours réécrire votre code pour qu'il ne soit plus nécessaire d'utiliser for...break. Vous pouvez filtrer array en utilisant .filter(), vous pouvez diviser array en utilisant .slice() et ainsi de suite, puis utiliser .forEach() ou .map() pour cette partie du tableau.

9
Max

Un autre concept que je suis venu avec:

function forEach(array, cb) {
  var shouldBreak;
  function _break() { shouldBreak = true; }
  for (var i = 0, bound = array.length; i < bound; ++i) {
    if (shouldBreak) { break; }
    cb(array[i], i, array, _break);
  }
}

// Usage

forEach(['a','b','c','d','e','f'], function (char, i, array, _break) {
  console.log(i, char);
  if (i === 2) { _break(); }
});
4
c24w

Trouvé cette solution sur un autre site. Vous pouvez envelopper le forEach dans un scénario try/catch.

if(typeof StopIteration == "undefined") {
 StopIteration = new Error("StopIteration");
}

try {
  [1,2,3].forEach(function(el){
    alert(el);
    if(el === 1) throw StopIteration;
  });
} catch(error) { if(error != StopIteration) throw error; }

Plus de détails ici: http://dean.edwards.name/weblog/2006/07/enum/

4
RussellUresti

C’est juste quelque chose que j’ai imaginé pour résoudre le problème ... Je suis à peu près sûr que cela corrige le problème rencontré par le demandeur initial:

Array.prototype.each = function(callback){
    if(!callback) return false;
    for(var i=0; i<this.length; i++){
        if(callback(this[i], i) == false) break;
    }
};

Et ensuite vous l'appeleriez en utilisant:

var myarray = [1,2,3];
myarray.each(function(item, index){
    // do something with the item
    // if(item != somecondition) return false; 
});

Renvoyer faux dans la fonction de rappel provoquera une pause. Faites-moi savoir si cela ne fonctionne pas réellement.

3
tennisgent

Si vous n'avez pas besoin d'accéder à votre tableau après l'itération, vous pouvez sortir en réglant la longueur du tableau sur 0. Si vous en avez toujours besoin après votre itération, vous pouvez le cloner à l'aide de slice.

[1,3,4,5,6,7,8,244,3,5,2].forEach(function (item, index, arr) {
  if (index === 3) arr.length = 0;
});

Ou avec un clone:

var x = [1,3,4,5,6,7,8,244,3,5,2];

x.slice().forEach(function (item, index, arr) {
  if (index === 3) arr.length = 0;
});

Ce qui est une bien meilleure solution que de jeter des erreurs aléatoires dans votre code.

3
3rdEden

Ceci est une boucle for, mais conserve la référence de l'objet dans la boucle, comme pour un forEach (), mais vous pouvez vous en sortir.

var arr = [1,2,3];
for (var i = 0, el; el = arr[i]; i++) {
    if(el === 1) break;
}
3
jamos

Comme mentionné précédemment, vous ne pouvez pas rompre .forEach().

Voici une manière un peu plus moderne de faire un foreach avec ES6 Iterators. Vous permet d’avoir un accès direct à index/value lors d’une itération.

const array = ['one', 'two', 'three'];

for (const [index, val] of array.entries()) {
  console.log('item:', { index, val });
  if (index === 1) {
    console.log('break!');
    break;
  }
}

Sortie:

item: { index: 0, val: 'one' }
item: { index: 1, val: 'two' }
break!

Liens

3
Alex

Si vous souhaitez conserver votre syntaxe forEach, il s'agit d'un moyen de la maintenir efficace (bien qu'elle ne soit pas aussi efficace qu'une boucle for normale). Recherchez immédiatement une variable qui sait si vous souhaitez sortir de la boucle.

Cet exemple utilise une fonction anonyme pour créer un étendue de la fonction autour de la forEach dont vous avez besoin pour stocker les informations done.

(function(){
    var element = document.getElementById('printed-result');
    var done = false;
    [1,2,3,4].forEach(function(item){
        if(done){ return; }
        var text = document.createTextNode(item);
        element.appendChild(text);
        if (item === 2){
          done = true;
          return;
        }
    });
})();
<div id="printed-result"></div>

Mes deux centimes.

1
Justus Romijn

Encore une autre approche

        var wageType = types.filter(function(element){
            if(e.params.data.text == element.name){ 
                return element;
            }
        });
        console.dir(wageType);
1
Harry Bosh

Je le sais pas comme il faut. Ce n'est pas casser la boucle. C'est un jugad

let result = true;
[1, 2, 3].forEach(function(el) {
    if(result){
      console.log(el);
      if (el === 2){
        result = false;
      }
    }
});
1
Durgpal Singh

J'utilise nullhack pour cela, il essaie d'accéder à la propriété de null, qui est une erreur:

try {
  [1,2,3,4,5]
  .forEach(
    function ( val, idx, arr ) {
      if ( val == 3 ) null.NULLBREAK;
    }
  );
} catch (e) {
  // e <=> TypeError: null has no properties
}
//
1
public override

vous pouvez suivre le code ci-dessous qui fonctionne pour moi:

 var     loopStop = false;
YOUR_ARRAY.forEach(function loop(){
    if(loopStop){ return; }
    if(condition){ loopStop = true; }
});
0

Utilisez la fonction array.prototype.every, qui vous fournit l’utilitaire pour interrompre la lecture en boucle. Voir exemple ici Documentation Javascript sur le réseau de développeurs Mozilla

0
Yiling

Je préfère utiliser for in

var words = ['a', 'b', 'c'];
var text = '';
for (x in words) {
    if (words[x] == 'b') continue;
    text += words[x];
}
console.log(text);

for in fonctionne beaucoup comme forEach, et vous pouvez ajouter la fonction return to exit à l'intérieur. Meilleure performance aussi.

0
Jorge Alberto

Ce n’est pas le plus efficace, car vous devez toujours parcourir tous les éléments, mais j’ai pensé que cela valait la peine d’envisager le très simple:

let keepGoing = true;
things.forEach( (thing) => {
  if (noMore) keepGoing = false;
  if (keepGoing) {
     // do things with thing
  }
});
0
martyman

essayez avec "trouver":

var myCategories = [
 {category: "start", name: "Start", color: "#AC193D"},
 {category: "action", name: "Action", color: "#8C0095"},
 {category: "exit", name: "Exit", color: "#008A00"}
];

function findCategory(category) {
  return myCategories.find(function(element) {
    return element.category === category;
  });
}

console.log(findCategory("start"));
// output: { category: "start", name: "Start", color: "#AC193D" }
0

D'accord avec @bobince, voté.

Aussi, FYI:

Prototype.js a quelque chose à cette fin:

<script type="text/javascript">
  $$('a').each(function(el, idx) {
    if ( /* break condition */ ) throw $break;
    // do something
  });
</script>

$break sera attrapé et traité par Prototype.js en interne, interrompant le cycle "chaque" mais ne générant pas d'erreurs externes.

Voir API Prototype.JS pour plus de détails.

jQuery a aussi un moyen, il suffit de retourner false dans le gestionnaire pour rompre la boucle plus tôt:

<script type="text/javascript">
  jQuery('a').each( function(idx) {
    if ( /* break condition */ ) return false;
    // do something

  });
</script>

Voir API jQuery pour plus de détails.

0
Dmitri Sologoubenko

Si vous devez effectuer une rupture en fonction de la valeur des éléments déjà présents dans votre tableau, comme dans votre cas (c.-à-d. Si la condition de rupture ne dépend pas de la variable d'exécution susceptible de changer après que le tableau a reçu la valeur de ses éléments), vous pouvez également utiliser la combinaison de slice () et indexOf () comme suit.

Si vous avez besoin de faire une pause quand forChaque atteint 'Apple', vous pouvez utiliser

var fruits = ["Banana", "Orange", "Lemon", "Apple", "Mango"];
var fruitsToLoop = fruits.slice(0, fruits.indexOf("Apple"));
// fruitsToLoop = Banana,Orange,Lemon

fruitsToLoop.forEach(function(el) {
    // no need to break
});

Comme indiqué dans W3Schools.com la méthode slice () renvoie les éléments sélectionnés dans un tableau, en tant que nouvel objet tableau. Le tableau d'origine ne sera pas modifié.

Voir dans JSFiddle

J'espère que ça aide quelqu'un.

0
Ula