Cortocircuito Array.forEach como llamada break

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

¿Cómo puedo hacer esto usando el nuevo forEachmétodo en JavaScript? Lo he intentado return;, return false;y break. breakse bloquea y returnno hace nada más que continuar la iteración.

Scott Klarenbach
fuente
66
Vale la pena señalar que, si bien returncontinúa la iteración, omitirá cualquier código que venga después en el bloque. Tome este código, por ejemplo: [1,2,3].forEach(function(el) { if(el === 2) { console.log(`Match on 2!`); return; } console.log(el); });.La console.log(el);se saltará cuando se empareja 2.
Shane
55
TL; DR: Ahorraré mucho tiempo a la mayoría de ustedes. He estado usando mucho JS últimamente. La respuesta (de 28 ...) que probablemente esté buscando es esta: stackoverflow.com/a/32101207/1599699
Andrew

Respuestas:

2143

No hay incorporado en capacidad de breaken forEach. Para interrumpir la ejecución, tendría que lanzar una excepción de algún tipo. p.ej.

var BreakException = {};

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

Las excepciones de JavaScript no son terriblemente bonitas. Un forbucle tradicional podría ser más apropiado si realmente lo necesita break.

Utilizar Array#some

En cambio, use Array#some:

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

Esto funciona porque someregresa truetan pronto como cualquiera de las devoluciones de llamada, ejecutadas en orden de matriz, regresa true, cortocircuitando la ejecución del resto.

some, es inverso every(que se detendrá en a return false), y forEachson todos los métodos ECMAScript Fifth Edition que deberán agregarse a los Array.prototypenavegadores en los que faltan.

bobince
fuente
111
Esto no es ni más fácil de leer ni más efectivo que el simple uso de un bucle normal. La respuesta debería ser "no usar para cada uno en este caso" -1
BT
37
Creo que "algunos" está bien aquí, ¿por qué no usar la optimización de salida temprana?
chrismarx
28
Gracias por pensar somey every, esto debería estar en TOP en la respuesta. No puedo entender por qué la gente piensa que es menos legible. ¡Es simplemente increíble!
Karl Adler
99
El uso de Array#somees realmente agradable. En primer lugar, es compatible con la mayoría de los navegadores, incluidos ie9 y firefox 1.5, también funciona muy bien. Mi ejemplo de uso será encontrar el índice en una matriz de rangos [a, b] donde un número está entre un límite inferior y un par de límite superior, probar y devolver verdadero cuando se encuentra. for..ofsería la siguiente mejor solución, aunque solo para navegadores más nuevos.
Sojimaxi
96
El manejo de excepciones NUNCA debe usarse como flujo de control. PERÍODO.
franco
479

Ahora hay una forma aún mejor de hacer esto en ECMAScript2015 (también conocido como ES6) usando el nuevo bucle for . Por ejemplo, este código no imprime los elementos de la matriz después del número 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 los documentos:

Tanto para ... en como para ... de declaraciones iteran sobre algo. La principal diferencia entre ellos está en lo que repiten. La instrucción for ... in itera sobre las propiedades enumerables de un objeto, en el orden de inserción original. La instrucción for ... of itera sobre los datos que el objeto iterable define para ser iterados.

¿Necesita el índice en la iteración? Puedes usar Array.entries():

for (const [index, el] of arr.entries()) {
  if ( index === 5 ) break;
}
canac
fuente
44
@superhero Puedes obtener el índice del elemento en un ciclo for ... of, solo tienes que usarlo entries. for (const [index, element] of someArray.entries ()) {// ...}
blackxored
¿No se recomienda no usar para ... con matrices?
schehata
44
@emostafa Tienes razón acerca de en bucles no está recomendada para las matrices, pero esto es en realidad utiliza un enfoque de de bucle.
canac
Esto es "para de", y esta es una solución realmente limpia ... pero también es una función de ES6, así que tenga en cuenta que esto solo funcionará si su entorno está configurado para ES6.
Chad
Me encuentro usando esta solución mucho, y también la uso para objetos. Con los objetos, puede hacerlo Object.entries(myObject)y luego usarlo exactamente como lo usa for..inpara la matriz. Tenga en cuenta que las matrices JS son básicamente objetos bajo el capó: blog.niftysnippets.org/2011/01/myth-of-arrays.html
Andrew
204

Puedes usar todos los métodos:

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

ES6

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

para usar el antiguo soporte de navegador:

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

Más detalles aquí .

Valdemar_Rudolfovich
fuente
10
Agradable y limpio en ES6 ahora -[1,2,3].every( el => el !== 1 )
metame
1
@Valdemar, ¿pero every garantiza que las llamadas se realicen en secuencia?
Pacerier
44
@Pacerier, puede ver el algoritmo en la especificación ES6 de que el índice kcomienza en 0 y se incrementa en 1: http://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.every
XP1
@ XP1, ¿se requiere que todos los implementadores lo hagan de esa manera?
Pacerier
1
@Pacerier, sí, las implementaciones más populares funcionan correctamente. Si le preocupan las implementaciones integradas, generalmente es Opera o webkit. Método cada llamada callbackfn una vez para cada elemento presente en la matriz, en orden ascendente , hasta que encuentre uno donde callbackfn devuelve falso. Observe también el paso 7. Deje k ser 0. y 8.e Aumente k en 1.
Valdemar_Rudolfovich
78

Citando de la documentación de MDN deArray.prototype.forEach() :

No hay forma de detener o romper un forEach()ciclo que no sea lanzando una excepción. Si necesita ese comportamiento, el .forEach()método es la herramienta incorrecta , utilice un bucle simple. Si está probando los elementos de la matriz para un predicado y necesita un valor de retorno booleano, puede usar every()o en su some()lugar.

Para su código (en la pregunta), como lo sugiere @bobince, use Array.prototype.some()en su lugar. Se adapta muy bien a su caso de uso.

Array.prototype.some()ejecuta la función de devolución de llamada una vez para cada elemento presente en la matriz hasta que encuentra una donde la devolución de llamada devuelve un valor verdadero (un valor que se convierte en verdadero cuando se convierte en a Boolean). Si se encuentra dicho elemento, some()inmediatamente devuelve verdadero. De lo contrario, some()devuelve falso. la devolución de llamada se invoca solo para índices de la matriz que tienen valores asignados; no se invoca para índices que se han eliminado o a los que nunca se les han asignado valores.

Rahul Desai
fuente
1
Esta es la respuesta correcta. 'some' hace exactamente lo que haría un foreach / break. Se repite hasta la iteración n = verdadero.
Antony Booth
74

Desafortunadamente en este caso será mucho mejor si no lo usas forEach. En su lugar, use un forbucle regular y ahora funcionará exactamente como es de esperar.

var array = [1, 2, 3];
for (var i = 0; i < array.length; i++) {
  if (array[i] === 1){
    break;
  }
}
Weston Ganger
fuente
27
Me sorprende que el voto más alto sea la peor implementación posible, en comparación con el rendimiento más alto, menos código y una mejor legibilidad de esta respuesta correcta. Lanzar excepción ... ¿en serio? ¿El bucle for tradicional no es suficiente?
gdbj
2
@gdbj Estoy de acuerdo con tu afirmación y utilicé este método, pero lo que realmente me sorprende es que no hay forma de salir de forEach sin estos hacks, ahora ese es un mal diseño.
ScottN
28

Considere utilizar jqueryel eachmétodo de, ya que permite devolver falso dentro de la función de devolución de llamada:

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

Las bibliotecas de Lodash también proporcionan un takeWhilemétodo que se puede encadenar con map / reduce / 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');
// => []
vittore
fuente
1
Buena razón para usar jQuery. forEach en javascript nativo todavía falta.
Alex Grande
3
@AlexGrande jQuery's forEach y JavaScript's forEach no son compatibles.
Bjorn
10
JavaScript se usa en muchos lugares donde jQuery no es una opción.
JBRWilkinson,
18

Si desea utilizar la sugerencia de Dean Edward y lanzar el error StopIteration para salir del ciclo sin tener que atrapar el error, puede usar la siguiente función ( originalmente desde aquí ):

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

El código anterior le dará la capacidad de ejecutar código como el siguiente sin tener que hacer sus propias cláusulas 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);
});

Una cosa importante para recordar es que esto solo actualizará la función Array.prototype.forEach si ya existe. Si aún no existe, no lo modificará.

Chris West
fuente
11

Respuesta corta: use for...breakesto o cambie su código para evitar romperlo forEach. No utilizar .some()ni .every()para emular for...break. Reescribe tu código para evitar el for...breakbucle, o úsalo for...break. Cada vez que usas estos métodos como for...breakalternativa, Dios mata al gatito.

Respuesta larga:

.some()y .every()ambos devuelven el booleanvalor, .some()devuelve truesi hay algún elemento para el que devuelve la función pasada true, cada devuelve falsesi hay algún elemento para el que devuelve la función pasadafalse . Esto es lo que significan las funciones. Usar funciones para lo que no significan es mucho peor que usar tablas para el diseño en lugar de CSS, porque frustra a todos los que leen su código.

Además, la única forma posible de utilizar estos métodos como for...breakalternativa es hacer efectos secundarios (cambiar algunos valores fuera de la .some()función de devolución de llamada), y esto no es muy diferente for...break.

Por lo tanto, usar .some()o .every()como for...breakalternativa de bucle no está libre de efectos secundarios, esto no es mucho más limpio for...break, es frustrante, por lo que no es mejor.

Siempre puede volver a escribir su código para que no sea necesario for...break. Puede filtrar la matriz usando .filter(), puede dividir la matriz usando .slice()y así sucesivamente, luego usar .forEach()o .map()para esa parte de la matriz.

Max
fuente
el uso de .filter es en realidad la solución adecuada para muchos casos de uso para romper
TKoL
¿Qué pasa con el rendimiento? ¿No afectará el filtro el rendimiento si se usa con frecuencia?
tfrascaroli
Sí, el prototipo de matriz de filtros puede ser pesado. Me encanta, pero podría afectar el rendimiento si se usa en exceso.
Chad
@tfrascaroli use for...breakloop si necesita rendimiento. forbucle es la herramienta más performant iteración que .forEach(), .any(), .map(), .filter()etc.
Max
6

Esto es algo que se me ocurrió para resolver el problema ... Estoy bastante seguro de que soluciona el problema que tenía el autor de la pregunta original:

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

Y luego lo llamarías usando:

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

Devolver falso dentro de la función de devolución de llamada provocará un descanso. Avísame si eso realmente no funciona.

tennisgent
fuente
1
=== falsepodría ser mejor que == falseno tener que devolver explícitamente verdadero (o un valor verdadero) para continuar el ciclo, no sea que alguna ruta de control no devuelva un valor y el ciclo se rompa inesperadamente.
Jake
6

Otro concepto que se me ocurrió:

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

c24w
fuente
La sintaxis es similar a [NSArray enumerateObjectsUsingBlock], ¡Gracias!
Chrstph SLN
@Drenai, la firma es análoga a la nativa Array.prototype.forEach(). fory breakexistía mucho antes de que se hiciera esta pregunta; el OP estaba buscando ese comportamiento usando, el más funcional forEach,.
c24w
@Drenai ahora ha eliminado su comentario (pero dejó el voto negativo) que mencionaba que la firma de esta solución es difícil de recordar e innecesaria cuando se puede resolver el problema con for...iny break.
c24w
6
var array = [1,2,3,4];

for(var item of array){
    console.log(item);
    if(item == 2){
       break;
    }
}
Surojit Paul
fuente
5

Encontré esta solución en otro sitio. Puede ajustar forEach en un escenario de prueba / captura.

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

Más detalles aquí: http://dean.edwards.name/weblog/2006/07/enum/

RussellUresti
fuente
2
No utilice excepciones como declaraciones de flujo de control. Úselo para manejar resultados inesperados.
Max
4

Si no necesita acceder a su matriz después de la iteración, puede rescatarla estableciendo la longitud de la matriz en 0. Si aún la necesita después de su iteración, podría clonarla usando un corte.

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

O con un clon:

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

Lo cual es una solución mucho mejor que arrojar errores aleatorios en su código.

3rdEden
fuente
bien hecho :) pero si hay algunas acciones después de la asignación array.lengtha 0que se aplicará en la iteración actual, por lo que, probablemente, a veces es mejor usar returndespués de dicha asignación
zhibirc
4

Este es un bucle for, pero mantiene la referencia del objeto en el bucle como un forEach () pero puede salir.

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

Como se mencionó anteriormente, no puedes romper .forEach().

Aquí hay una forma un poco más moderna de hacer un foreach con los iteradores ES6. Le permite obtener acceso directo a index/ valueal iterar.

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

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

Salida:

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

Enlaces

Alex
fuente
3

Otro enfoque

        var wageType = types.filter(function(element){
            if(e.params.data.text == element.name){ 
                return element;
            }
        });
        console.dir(wageType);
Harry Bosh
fuente
¿Es este el uso correcto del método de filtro? Supongo que la devolución de llamada devuelve un valor booleano que, independientemente de eso, la devolución de llamada se llama elementos de matriz hasta el último.
MortezaE
2

Utilizo nullhack para ese propósito, intenta acceder a la propiedad de null, que es un error:

try {
  [1,2,3,4,5]
  .forEach(
    function ( val, idx, arr ) {
      if ( val == 3 ) null.NULLBREAK;
    }
  );
} catch (e) {
  // e <=> TypeError: null has no properties
}
//
anulación pública
fuente
1
¿Por qué no solo throw BREAK?
Bergi
1

Si desea mantener su forEachsintaxis, esta es una manera de mantenerla eficiente (aunque no tan buena como un ciclo regular). Verifique de inmediato si hay una variable que sepa si desea salir del ciclo.

Este ejemplo utiliza una función anónima para crear un ámbito de función alrededor del forEachcual necesita almacenar la información realizada .

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

Mis dos centavos.

Justus Romijn
fuente
1

Sé que no es lo correcto. No es romper el ciclo. Es un jugad

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

Durgpal Singh
fuente
0

De acuerdo con @bobince, upvoted.

Además, para su información:

Prototype.js tiene algo para este propósito:

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

$break será capturado y manejado por Prototype.js internamente, rompiendo el ciclo "cada" pero sin generar errores externos.

Ver Prototype.JS API para más detalles.

jQuery también tiene una forma, solo devuelve false en el controlador para romper el ciclo temprano:

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

  });
</script>

Ver jQuery API para más detalles.

Dmitri Sologoubenko
fuente
0

Este no es el más eficiente, ya que todavía se ciclan todos los elementos, pero pensé que valdría la pena considerar lo más simple:

let keepGoing = true;
things.forEach( (thing) => {
  if (noMore) keepGoing = false;
  if (keepGoing) {
     // do things with thing
  }
});
martyman
fuente
continuees una palabra clave, su código es un error de sintaxis.
Bergi
3
Dado que de todos modos está utilizando ES6, debe cambiar a un for ofbucle y, break;como de costumbre, de eso.
Bergi
fijo y verdadero, pero estaba usando principalmente es6 por brevedad
martyman
0

puedes seguir el siguiente código que funciona para mí:

 var     loopStop = false;
YOUR_ARRAY.forEach(function loop(){
    if(loopStop){ return; }
    if(condition){ loopStop = true; }
});
BERGUIGA Mohamed Amine
fuente
¿Por qué el -1? no es más feo que atrapar una excepción, ese es un truco más grande en mi humilde opinión.
Byron Whitlock
0

Prefiero usar 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 infunciona de manera similar forEach, y puede agregar la función de retorno a la salida dentro. Mejor rendimiento también.

Jorge Alberto
fuente
0

Si necesita dividir en función del valor de los elementos que ya están en su matriz como en su caso (es decir, si la condición de ruptura no depende de la variable de tiempo de ejecución que puede cambiar después de que se asignen los valores de los elementos a la matriz), también puede usar la combinación de slice () e indexOf () de la siguiente manera.

Si necesita romper cuando forEach llega a 'Apple', puede usar

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

Como se indica en W3Schools.com el método slice () devuelve los elementos seleccionados en una matriz, como un nuevo objeto de matriz. La matriz original no se cambiará.

Véalo en JSFiddle

Espero que ayude a alguien.

Ula
fuente
0

Se puede crear una variante de forEachque permite break, continue, return, e incluso async/ await: (ejemplo escrito a máquina)

export type LoopControlOp = "break" | "continue" | ["return", any];
export type LoopFunc<T> = (value: T, index: number, array: T[])=>LoopControlOp;

Array.prototype.ForEach = function ForEach<T>(this: T[], func: LoopFunc<T>) {
    for (let i = 0; i < this.length; i++) {
        const controlOp = func(this[i], i, this);
        if (controlOp == "break") break;
        if (controlOp == "continue") continue;
        if (controlOp instanceof Array) return controlOp[1];
    }
};

// this variant lets you use async/await in the loop-func, with the loop "awaiting" for each entry
Array.prototype.ForEachAsync = async function ForEachAsync<T>(this: T[], func: LoopFunc<T>) {
    for (let i = 0; i < this.length; i++) {
        const controlOp = await func(this[i], i, this);
        if (controlOp == "break") break;
        if (controlOp == "continue") continue;
        if (controlOp instanceof Array) return controlOp[1];
    }
};

Uso:

function GetCoffee() {
    const cancelReason = peopleOnStreet.ForEach((person, index)=> {
        if (index == 0) return "continue";
        if (person.type == "friend") return "break";
        if (person.type == "boss") return ["return", "nevermind"];
    });
    if (cancelReason) console.log("Coffee canceled because: " + cancelReason);
}
Venryx
fuente
-1

intente con "buscar":

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" }
GigolNet Guigolachvili
fuente
-1

Sí, es posible continuar y salir de un bucle forEach.

Para continuar, puede usar return, el ciclo continuará pero la función actual finalizará.

Para salir del bucle, puede establecer el tercer parámetro a 0 de longitud, establecer en una matriz vacía. El bucle no continuará, la función actual sí, por lo que puede usar "volver" para terminar, como salir en un bucle normal para ...

Esta:

[1,2,3,4,5,6,7,8,9,10].forEach((a,b,c) => {
    console.log(a);
    if(b == 2){return;}
    if(b == 4){c.length = 0;return;}
    console.log("next...",b);
});

imprimirá esto:

1
next... 0
2
next... 1
3
4
next... 3
5
Luis Marin
fuente
-2

Antes, mi código está debajo

 this.state.itemsDataSource.forEach((item: any) => {
                if (!item.isByPass && (item.invoiceDate == null || item.invoiceNumber == 0)) {

                    return false;
                }
            });

He cambiado a continuación, se solucionó.

 for (var i = 0; i < this.state.itemsDataSource.length; i++) {
                var item = this.state.itemsDataSource[i];
                if (!item.isByPass && (item.invoiceDate == null || item.invoiceNumber == 0)) {

                    return false;
                }
            }
Metin Atalay
fuente