Array de curto-circuito. Para cada chamada de interrupção


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

Como posso fazer isso usando o novo forEachmétodo em JavaScript? Eu tentei return;, return false;e break. breaktrava e returnnão faz nada além de continuar a iteração.


6
Vale a pena notar que, embora de returnfato continue a iteração, pulará qualquer código que vier depois dele no bloco. Tome este código, por exemplo: [1,2,3].forEach(function(el) { if(el === 2) { console.log(`Match on 2!`); return; } console.log(el); });.O console.log(el);será ignorado quando 2 corresponder.
Shane

5
TL; DR: Economizo muito tempo para a maioria de vocês. Eu tenho usado muito JS ultimamente. A resposta (de 28 ...) que provavelmente você está procurando um presente: stackoverflow.com/a/32101207/1599699
Andrew

Respostas:


2143

Não há como built-in capacidade de breaknos forEach. Para interromper a execução, você teria que lançar uma exceção de algum tipo. por exemplo.

var BreakException = {};

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

As exceções de JavaScript não são muito bonitas. Um forloop tradicional pode ser mais apropriado se você realmente precisar breakdele.

Usar Array#some

Em vez disso, use Array#some:

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

Isso funciona porque someretorna trueassim que qualquer retorno de chamada, executado em ordem de matriz, retorna true, causando um curto-circuito na execução do restante.

some, é inverso every(que será interrompido em a return false) e forEachsão todos os métodos do ECMAScript Fifth Edition que precisarão ser adicionados aos Array.prototypenavegadores nos quais estão ausentes.


111
Isso não é mais legível, nem mais eficiente do que apenas usar um loop for normal. A resposta deve ser "não use forEach neste caso" -1
BT

37
Eu acho que "alguns" está bem aqui, por que não usar a saída precoce Optimization
chrismarx

28
Obrigado pela atenção somee every, isso deve estar no TOP na resposta. Não consigo entender por que as pessoas pensam que é menos legível. É simplesmente incrível!
Karl Adler

9
O uso de Array#someé muito bom. Em primeiro lugar, é compatível com a maioria dos navegadores, incluindo ie9 e firefox 1.5, também funciona muito bem. Meu exemplo de caso de uso será encontrar o índice em uma matriz de intervalos [a, b] em que um número esteja entre um par de limite inferior e superior, testar e retornar true quando encontrado. for..ofseria a próxima melhor solução, embora apenas para navegadores mais recentes.
Sojimaxi 2/11

96
O manuseio de exceção NUNCA deve ser usado como fluxo de controle. PERÍODO.
Frank

479

Agora existe uma maneira ainda melhor de fazer isso no ECMAScript2015 (também conhecido como ES6) usando o novo loop for . Por exemplo, este código não imprime os elementos da matriz após o 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;
  }
}

Dos documentos:

Tanto para ... como para ... das declarações repetem algo. A principal diferença entre eles está no que eles repetem. A instrução for ... itera sobre as propriedades enumeráveis ​​de um objeto, na ordem de inserção original. A instrução for ... itera sobre os dados que o objeto iterável define para ser iterado.

Precisa do índice na iteração? Você pode usar Array.entries():

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

4
@ superhero Você pode obter o índice do elemento em um loop for ..., basta usar entries. para (const [index, elemento] de someArray.entries ()) {// ...}
blackxored

não é recomendado não usar para ... com matrizes?
schehata

4
@emostafa Você está correto em ter em circuitos não sendo recomendado para matrizes, mas esta é a abordagem realmente usa um para de loop.
21418 canac

Isso é "for of" e é uma solução realmente limpa ... mas também é um recurso do ES6, portanto, lembre-se de que isso só funcionará se o seu ambiente estiver configurado para o ES6.
Chad

Encontro-me usando muito esta solução e também a uso para objetos. Com objetos, você pode fazer Object.entries(myObject)e usá-lo exatamente como você usa for..inpara o array. Observe que as matrizes JS são basicamente objetos ocultos
Andrew

204

Você pode usar todos os métodos:

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

ES6

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

para suporte ao navegador antigo, use:

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

mais detalhes aqui .


10
Bom e limpo no ES6 agora -[1,2,3].every( el => el !== 1 )
metame

1
@ Valdemar, mas every garante que as chamadas sejam feitas em sequência?
Pacerier

4
@Pacerier, você pode ver o algoritmo na especificação ES6 de que o índice kinicia em 0 e é incrementado por 1: http://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.every
XP1 26/06

@ XP1, são necessários todos os implementadores para fazê-lo dessa maneira?
Pacerier 7/08/17

1
@ Pacerier, sim, as implementações mais populares funcionam corretamente. Se você está preocupado com implementações incorporadas, geralmente é Opera ou webkit. Método todas as chamadas callbackfn uma vez para cada elemento presente na matriz, em ordem crescente , até encontrar uma em que callbackfn retorna false. Também olhar para a etapa 7. Seja K 0. ek Aumento 8.e por 1.
Valdemar_Rudolfovich

78

Citando a partir da documentação MDN deArray.prototype.forEach() :

Não há como parar ou interromper um forEach()loop além de lançar uma exceção. Se você precisar desse comportamento, o .forEach()método é a ferramenta errada , use um loop simples. Se você estiver testando os elementos da matriz em busca de um predicado e precisar de um valor de retorno booleano, poderá usar every()ou em some()vez disso.

Para o seu código (na pergunta), conforme sugerido por @bobince, use em seu Array.prototype.some()lugar. Serve muito bem para o seu caso.

Array.prototype.some()executa a função de retorno de chamada uma vez para cada elemento presente na matriz até encontrar um em que o retorno de chamada retorne um valor verdadeiro (um valor que se torna verdadeiro quando convertido em a Boolean). Se esse elemento for encontrado, some()retornará imediatamente true. Caso contrário, some()retorna false. o retorno de chamada é invocado apenas para índices da matriz que atribuíram valores; não é invocado para índices que foram excluídos ou aos quais nunca foram atribuídos valores.


1
Essa é a resposta correta. 'some' faz exatamente o que um foreach / break faria. Faz um loop até a iteração n = true.
Antony Booth

74

Infelizmente, neste caso, será muito melhor se você não usar forEach. Em vez disso, use um forloop regular e agora funcionará exatamente como você esperaria.

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

27
Me choca que o voto mais alto seja a pior implementação possível, em comparação com o melhor desempenho, menos código e melhor legibilidade dessa resposta correta. Lançar exceção ... realmente? O loop for tradicional não é suficiente?
Gdbj #

2
@gdbj Concordo com sua afirmação e usei esse método, mas o que realmente me choca é que não há como sair de um forEach sem esses hacks, agora isso é um design ruim.
precisa saber é o seguinte

28

Considere usar jqueryo eachmétodo, pois permite retornar a função de retorno de chamada interna falsa:

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

As bibliotecas Lodash também fornecem um takeWhilemétodo que pode ser encadeado com 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');
// => []

1
Bom motivo para usar o jQuery. ainda está faltando o forEach em javascript nativo.
Alex Grande

3
@AlexGrande forEach do jQuery e forEach do JavaScript não são compatíveis.
Bjorn

10
O JavaScript é usado em muitos lugares em que o jQuery não é uma opção.
JBRWilkinson


18

Se você quiser usar a sugestão de Dean Edward e lançar o erro StopIteration para interromper o loop sem precisar capturar o erro, use a seguinte função ( originalmente daqui ):

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

O código acima permitirá executar códigos como os seguintes, sem a necessidade de fazer suas próprias 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);
});

Uma coisa importante a lembrar é que isso só atualizará a função Array.prototype.forEach se ela já existir. Se ainda não existir, não será modificado.


11

Resposta curta: use for...breakpara isso ou altere seu código para evitar a quebra de forEach. Não use .some()ou .every()para emular for...break. Reescreva seu código para evitar for...breakloop ou use for...break. Toda vez que você usa esses métodos como for...breakalternativa, Deus mata um gatinho.

Resposta longa:

.some()e .every()ambos retornam booleanvalor, .some()retornam truese houver algum elemento para o qual a função passada retorne true, todos retornarão falsese houver algum elemento para o qual a função passada retorne false. É isso que essas funções significam. Usar funções para o que elas não significam é muito pior do que usar tabelas para layout em vez de CSS, porque frustra todo mundo que lê seu código.

Além disso, a única maneira possível de usar esses métodos como for...breakalternativa é produzir efeitos colaterais (alterar alguns vars fora da .some()função de retorno de chamada), e isso não é muito diferente for...break.

Portanto, usar .some()ou .every()como for...breakalternativa de loop não está isento de efeitos colaterais, não é muito mais limpo for...break, é frustrante, então não é melhor.

Você sempre pode reescrever seu código para que não haja necessidade for...break. Você pode filtrar a matriz usando .filter(), pode dividir a matriz usando .slice()e assim por diante, depois usar .forEach()ou .map()para essa parte da matriz.


usar .filter é na verdade a solução apropriada para muitos casos de uso para interrupção.
TKoL

E o desempenho? O filtro não afeta o desempenho se usado com frequência?
Tfrascaroli 25/05

Sim, o protótipo da matriz de filtros pode ficar pesado. Adoro, mas pode afetar o desempenho se for usado em excesso.
Chad

@tfrascaroli use for...breakloop se precisar de desempenho. forlaço é a ferramenta mais perfeita do que iteração .forEach(), .any(), .map(), .filter()etc
Max

6

Isso é apenas algo que eu criei para resolver o problema ... Tenho certeza de que corrige o problema que o solicitante original tinha:

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

E então você chamaria usando:

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

Retornar false dentro da função de retorno de chamada causará uma interrupção. Deixe-me saber se isso realmente não funciona.


1
=== falsepode ser melhor do que == falseisso, para que você não precise retornar explicitamente true (ou um valor verdadeiro) para continuar o loop, para que algum caminho de controle não retorne um valor e o loop quebre inesperadamente.
Jake

6

Outro conceito que surgiu:

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


A sintaxe é semelhante a [NSArray enumerateObjectsUsingBlock], Obrigado!
Chrstph SLN

@ Denrenai a assinatura é análoga à nativa Array.prototype.forEach(). fore breakexistia muito antes desta pergunta ser feita; o OP estava procurando esse comportamento usando, o mais funcional forEach,.
C24w 23/10/19

A @Drenai agora excluiu o comentário (mas deixou o voto negativo) que mencionava que a assinatura desta solução é difícil de lembrar e desnecessária quando você pode resolver o problema com for...ine break.
C24w # 25/19

6
var array = [1,2,3,4];

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

5

Encontrei esta solução em outro site. Você pode agrupar o forEach em um cenário de tentativa / 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; }

Mais detalhes aqui: http://dean.edwards.name/weblog/2006/07/enum/


2
Não use exceções como instruções de fluxo de controle. Use-o para lidar com resultados inesperados.
Max

4

Se você não precisar acessar sua matriz após a iteração, poderá resgatar definindo o comprimento da matriz como 0. Se ainda precisar dela após a iteração, poderá cloná-la usando a fatia.

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

Ou com um 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;
});

Qual é uma solução muito melhor do que lançar erros aleatórios no seu código.


bem feito :) mas se existem algumas ações após atribuir array.lengtha 0elas se aplicarão em iteração atual, então provavelmente às vezes é melhor usar returndepois de tal atribuição
zhibirc

4

Este é um loop for, mas mantém a referência do objeto no loop como um forEach (), mas você pode sair.

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

4

Como mencionado anteriormente, você não pode quebrar .forEach().

Aqui está uma maneira um pouco mais moderna de fazer um foreach com os Iteradores ES6. Permite que você obtenha acesso direto a index/ valuedurante a iteração.

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

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

Resultado:

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

Ligações


3

Ainda outra abordagem

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

2

Eu uso o nullhack para esse fim, ele tenta acessar a propriedade de null, que é um erro:

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
Por que não apenas throw BREAK?
Bergi

1

Se você deseja manter sua forEachsintaxe, esta é uma maneira de mantê-la eficiente (embora não tão boa quanto um loop for normal). Verifique imediatamente se há uma variável que saiba se você deseja interromper o loop.

Este exemplo usa uma função anônima para criar um escopo de função em torno do forEachqual você precisa armazenar as informações concluídas .

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

Meus dois centavos.


1

Eu sei que não é o caminho certo. Não é quebrar o ciclo. É um Jugad

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



0

Concordo com @bobince, votado.

Além disso, para sua informação:

O Prototype.js tem algo para esse fim:

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

$break serão capturados e manipulados por Prototype.js internamente, interrompendo o ciclo "cada", mas sem gerar erros externos.

Consulte API Prototype.JS para obter detalhes.

O jQuery também tem uma maneira, basta retornar false no manipulador para interromper o loop mais cedo:

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

  });
</script>

Consulte a API do jQuery para obter detalhes.


0

Isso não é o mais eficiente, já que você ainda alterna todos os elementos, mas achei que poderia valer a pena considerar o muito simples:

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

continueé uma palavra-chave, seu código é um erro de sintaxe.
21915 Bergi

3
Como você está usando o ES6 de qualquer maneira, basta alternar para um for ofloop e a break;partir disso, como de costume.
21915 Bergi

fixa, e verdade - mas foi principalmente usando ES6 por brevidade
martyman

0

você pode seguir o código abaixo, que funciona para mim:

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

Por que o -1? não é mais feio do que pegar uma exceção, que é um IMHO maior de hackers.
Byron Whitlock

0

Eu prefiro 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 da mesma forma forEache você pode adicionar a função de retorno para sair dentro. Melhor desempenho também.


0

Se você precisar quebrar com base no valor dos elementos que já estão em sua matriz, como no seu caso (ou seja, se a condição de interrupção não depender da variável em tempo de execução que pode mudar após a matriz receber seus valores de elemento), você também pode usar a combinação de fatia () e indexOf () seguinte maneira.

Se precisar interromper quando forEach chegar a 'Apple', você pode 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
});

Conforme declarado em W3Schools.com, o método slice () retorna os elementos selecionados em uma matriz, como um novo objeto de matriz. A matriz original não será alterada.

Veja no JSFiddle

Espero que ajude alguém.


0

Você pode criar uma variante forEachque permite break, continue, return, e mesmo async/ await: (exemplo escrito à 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);
}

-1

tente com "encontrar":

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

-1

Sim, é possível continuar e sair de um loop forEach.

Para continuar, você pode usar return, o loop continuará, mas a função atual terminará.

Para sair do loop, você pode definir o terceiro parâmetro como comprimento 0, definido como matriz vazia. O loop não continua, a função atual continua, então você pode usar "return" para finalizar, como sair de um loop normal para ...

Este:

[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á isto:

1
next... 0
2
next... 1
3
4
next... 3
5

-2

Antes, meu código está abaixo

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

                    return false;
                }
            });

Eu mudei para abaixo, foi corrigido.

 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;
                }
            }
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.