Array di cortocircuito per ogni tipo di chiamata break


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

Come posso farlo usando il nuovo forEachmetodo in JavaScript? Ho provato return;, return false;e break. breaksi arresta in modo anomalo e returnnon fa altro che continuare l'iterazione.


6
Vale la pena notare che mentre in returneffetti continua l'iterazione, salterà qualsiasi codice che lo segue nel blocco. Prendete questo codice per esempio: [1,2,3].forEach(function(el) { if(el === 2) { console.log(`Match on 2!`); return; } console.log(el); });.La console.log(el);devono essere saltati quando 2 è abbinato.
Shane,

5
TL; DR: Ti risparmierò un sacco di tempo. Ultimamente ho usato un sacco di JS. La risposta (su 28 ...) che probabilmente stai cercando è questa: stackoverflow.com/a/32101207/1599699
Andrew

Risposte:


2143

Non c'è built-in capacità di breaka forEach. Per interrompere l'esecuzione dovresti lanciare un'eccezione di qualche tipo. per esempio.

var BreakException = {};

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

Le eccezioni JavaScript non sono terribilmente belle. Un forloop tradizionale potrebbe essere più appropriato se davvero necessariobreak interno.

Uso Array#some

Invece, usa Array#some:

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

Questo funziona perché somerestituisce truenon appena uno dei callback, eseguito in ordine di array, restituisce true, cortocircuitando l'esecuzione del resto.

some, è inverso every(che si fermerà su a return false) e forEachsono tutti i metodi ECMAScript di quinta edizione che dovranno essere aggiunti ai Array.prototypebrowser in cui mancano.


111
Questo non è né più leggibile, né più performante del semplice utilizzo di un normale ciclo for. La risposta dovrebbe essere "non usare per ogni caso in questo caso" -1
BT

37
Penso che "alcuni"
vadano

28
Grazie per la mentalità somee every, questo dovrebbe essere al top nella risposta. Non riesco a capire perché la gente pensi che sia meno leggibile. È semplicemente fantastico!
Karl Adler,

9
L'uso di Array#someè davvero bello. In primo luogo è compatibile con la maggior parte dei browser, inclusi ie9 e Firefox 1.5. Il mio caso d'uso di esempio sarà trovare l'indice in una matrice di intervalli [a, b] in cui un numero si trova tra un limite inferiore e una coppia di limiti superiore, testare e restituire vero quando trovato. for..ofsarebbe la soluzione migliore successiva anche se solo per i browser più recenti.
Sojimaxi,

96
La gestione delle eccezioni non deve MAI essere utilizzata come flusso di controllo. PERIODO.
franco

479

Ora c'è un modo ancora migliore per farlo in ECMAScript2015 (aka ES6) usando il nuovo for of loop . Ad esempio, questo codice non stampa gli elementi dell'array dopo il numero 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;
  }
}

Dai documenti:

Sia per ... in che per ... delle affermazioni ripetono qualcosa. La principale differenza tra loro è in ciò che ripetono. L' istruzione for ... in scorre le enumerabili proprietà di un oggetto, nell'ordine di inserimento originale. L' istruzione for ... of esegue un'iterazione sui dati che l'oggetto iterable definisce per essere iterati.

Hai bisogno dell'indice nell'iterazione? Puoi usare Array.entries():

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

4
@superhero Puoi ottenere l'indice dell'elemento in un ciclo for ... of, devi solo usare entries. for (const [indice, elemento] di someArray.entries ()) {// ...}
blackxored

non è consigliato non usare per ... in con gli array?
Schehata,

4
@emostafa Lei ha ragione su per in loop non essere raccomandato per gli array, ma questo è l'approccio in realtà utilizza una per di ciclo.
canac,

Questo è "for of" e questa è una soluzione davvero pulita ... ma questa è anche una funzione ES6, quindi tieni presente che funzionerà solo se il tuo ambiente è configurato per ES6.
Chad,

Mi trovo a usare questa soluzione molto e la utilizzo anche per gli oggetti. Con gli oggetti, puoi fare Object.entries(myObject)e poi usarlo esattamente come usi for..inper l'array. Si noti che gli array JS sono sostanzialmente oggetti sotto il cofano: blog.niftysnippets.org/2011/01/myth-of-arrays.html
Andrew

204

Puoi usare ogni metodo:

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

ES6

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

per il supporto del vecchio browser utilizzare:

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

maggiori dettagli qui .


10
Bello e pulito in ES6 ora -[1,2,3].every( el => el !== 1 )
metame

1
@Valdemar, ma every garantisce che le chiamate vengano effettuate in sequenza?
Pacerier

4
@Pacerier, puoi vedere l'algoritmo nella specifica ES6 che l'indice kinizia da 0 e viene incrementato di 1: http://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.every
XP1,

@ XP1, Tutti gli implementatori sono tenuti a farlo in questo modo?
Pacerier

1
@Pacerier, sì, le implementazioni più popolari funzionano correttamente. Se sei preoccupato per le implementazioni integrate, di solito è Opera o webkit. Metodo ogni chiamata callbackfn una volta per ogni elemento presente nell'array, in ordine crescente , fino a quando non trova uno in cui callbackfn restituisce false. Guarda anche il passaggio 7. Sia k pari a 0. e 8.e Aumenta k di 1.
Valdemar_Rudolfovich

78

Citando dalla documentazione MDN diArray.prototype.forEach() :

Non c'è modo di interrompere o interrompere un forEach()loop se non lanciando un'eccezione. Se hai bisogno di tale comportamento, il .forEach()metodo è lo strumento sbagliato , usa invece un semplice ciclo. Se stai testando gli elementi dell'array per un predicato e hai bisogno di un valore di ritorno booleano, puoi usare every()o some()invece.

Per il tuo codice (nella domanda), come suggerito da @bobince, usa Array.prototype.some()invece. Si adatta molto bene al tuo caso d'uso.

Array.prototype.some()esegue la funzione di callback una volta per ogni elemento presente nell'array fino a quando non ne trova uno in cui il callback restituisce un valore di verità (un valore che diventa vero quando convertito in a Boolean). Se viene trovato un tale elemento, some()restituisce immediatamente true. Altrimenti, some()restituisce false. il callback viene richiamato solo per gli indici dell'array a cui sono stati assegnati valori; non viene invocato per gli indici che sono stati eliminati o ai quali non sono mai stati assegnati valori.


1
Questa è la risposta corretta 'some' fa esattamente quello che farebbe una foreach / break. Esegue il loop fino all'iterazione n = true.
Antony Booth,

74

Sfortunatamente in questo caso sarà molto meglio se non lo usi forEach. Usa invece un forciclo regolare e ora funzionerà esattamente come ti aspetteresti.

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

27
Mi sorprende che il voto più alto sia la peggiore implementazione possibile, rispetto al rendimento più elevato, meno codice e una migliore leggibilità di questa risposta corretta. Lancia un'eccezione ... davvero? Il ciclo tradizionale non è abbastanza kewl?
gdbj,

2
@gdbj Sono d'accordo con la tua affermazione e ho usato questo metodo, ma ciò che mi ha davvero scioccato è che non c'è modo di uscire da un forEach senza questi hack, ora è un cattivo design.
ScottN,

28

Si consideri di utilizzare jqueryil eachmetodo, poiché consente di restituire false all'interno della funzione di callback:

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

Le librerie di Lodash forniscono anche un takeWhilemetodo che può essere concatenato con mappa / riduci / piega ecc:

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
Un buon motivo per usare jQuery. forOach in JavaScript nativo manca ancora.
Alex Grande,

3
@AlexGrande forEach di jQuery e forEach di JavaScript non sono compatibili.
Bjorn,

10
JavaScript è utilizzato in molti luoghi in cui jQuery non è un'opzione.
JBR Wilkinson,


18

Se desideri utilizzare il suggerimento di Dean Edward e lanciare l'errore StopIteration per uscire dal ciclo senza dover catturare l'errore, puoi utilizzare la seguente funzione ( originariamente da qui ):

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

Il codice sopra ti darà la possibilità di eseguire codice come il seguente senza dover fare le tue clausole 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 da ricordare è che questo aggiornerà la funzione Array.prototype.forEach solo se esiste già. Se non esiste già, non lo modificherà.


11

Risposta breve: utilizzare for...breakper questo o modificare il codice per evitare la rottura di forEach. Non usare .some()o .every()emulare for...break. Riscrivi il codice per evitare il for...breakloop o utilizzalo for...break. Ogni volta che usi questi metodi comefor...break alternativa, Dio uccide il gattino.

Risposta lunga:

.some()ed .every()entrambi restituiscono booleanvalore, .some()restituisce truese c'è un elemento per il quale restituisce la funzione passata true, ogni restituisce falsese c'è un elemento per il quale restituisce la funzionefalse . Questo è ciò che significano queste funzioni. Usare le funzioni per ciò che non significano è molto peggio che usare le tabelle per il layout anziché i CSS, perché frustrano tutti coloro che leggono il tuo codice.

Inoltre, l'unico modo possibile per utilizzare questi metodi in for...breakalternativa è creare effetti collaterali (modificare alcuni parametri al di fuori della .some()funzione di callback), e questo non è molto diverso da for...break.

Quindi, l'utilizzo .some()o .every()come for...breakalternativa al ciclo non è privo di effetti collaterali, quindi non è molto più pulitofor...break , è frustrante, quindi non è meglio.

Puoi sempre riscrivere il tuo codice in modo che non sia necessario for...break. È possibile filtrare l'array utilizzando .filter(), è possibile dividere l'array utilizzando .slice()e così via, quindi utilizzare .forEach()o .map()per quella parte dell'array.


l'uso di .filter è in realtà la soluzione appropriata per molti casi d'uso per la rottura.
TKoL,

E le prestazioni? Il filtro non influisce sulle prestazioni se usato frequentemente?
tfrascaroli,

Sì, il prototipo di array di filtri può diventare pesante. Lo adoro, ma potrebbe influire sulle prestazioni se è abusato.
Chad,

@tfrascaroli usa il for...breakloop se hai bisogno di prestazioni. forloop è la più performante strumento di iterazione che .forEach(), .any(), .map(), .filter()ecc
Max

6

Questo è solo qualcosa che mi è venuto in mente per risolvere il problema ... Sono abbastanza sicuro che risolva il problema che il richiedente originale aveva:

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 poi lo chiameresti usando:

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

Restituire false all'interno della funzione di callback provocherà un'interruzione. Fammi sapere se in realtà non funziona.


1
=== falsepotrebbe essere meglio di == falsecosì non è necessario restituire esplicitamente true (o un valore di verità) per continuare il ciclo, per timore che un percorso di controllo non restituisca un valore e il ciclo si interrompa in modo imprevisto.
Jake,

6

Un altro concetto che mi è venuto in mente:

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


La sintassi è simile a [NSArray enumerateObjectsUsingBlock], grazie!
Chrstph SLN,

@Drenai la firma è analoga al nativo Array.prototype.forEach(). fored breakesisteva molto prima che questa domanda fosse posta; l'OP stava cercando quel comportamento usando, più funzionale forEach,.
c24w,

@Drenai ha ora eliminato il loro commento (ma ha lasciato il downvote) che ha affermato che la firma di questa soluzione è difficile da ricordare e non necessaria quando è possibile risolvere il problema con for...ine break.
c24w,

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

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

5

Ho trovato questa soluzione su un altro sito. Puoi avvolgere forEach in uno scenario 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; }

Maggiori dettagli qui: http://dean.edwards.name/weblog/2006/07/enum/


2
Non utilizzare le eccezioni come istruzioni di flusso di controllo. Usalo per gestire risultati imprevisti.
Max

4

Se non è necessario accedere all'array dopo l'iterazione, è possibile eseguire il salvataggio impostando la lunghezza dell'array su 0. Se è ancora necessario dopo l'array, è possibile clonarlo utilizzando slice ..

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

Quale è una soluzione molto migliore quindi lanciare errori casuali nel tuo codice.


ben fatto :) ma se ci sono alcune azioni dopo l'assegnazione array.lengthad 0essi si applicano in iterazione corrente, quindi probabilmente a volte è meglio usare returndopo tale assegnazione
zhibirc

4

Questo è un ciclo for, ma mantiene il riferimento all'oggetto nel ciclo proprio come un forEach () ma è possibile interrompere.

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

4

Come accennato in precedenza, non puoi rompere .forEach().

Ecco un modo leggermente più moderno di fare una foreach con ES6 Iterators. Consente di ottenere l'accesso diretto a index/ valuedurante l'iterazione.

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

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

Produzione:

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

link


3

Ancora un altro approccio

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

Questo uso corretto del metodo di filtro? Immagino che callback restituisca un valore booleano che, indipendentemente da esso, callback è chiamato elementi dell'array fino all'ultimo.
MortezaE

2

Uso nullhack a tale scopo, tenta di accedere alla proprietà di null, che è un errore:

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
Perché non solo throw BREAK?
Bergi,

1

Se vuoi mantenere la tua forEachsintassi, questo è un modo per mantenerlo efficiente (anche se non buono come un normale ciclo). Cerca immediatamente una variabile che sappia se vuoi uscire dal ciclo.

In questo esempio viene utilizzata una funzione anonima per creare un ambito di funzione attorno al forEachquale è necessario archiviare le informazioni fatte .

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

I miei due centesimi.


1

Lo so non nel modo giusto. Non si interrompe il ciclo. È una Jugad

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



0

Concordo con @bobince, votato.

Inoltre, FYI:

Prototype.js ha qualcosa per questo scopo:

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

$break verrà catturato e gestito internamente da Prototype.js, interrompendo il ciclo "ogni" ma non generando errori esterni.

Consulta l' API Prototype.JS per i dettagli.

jQuery ha anche un modo, basta restituire false nel gestore per interrompere anticipatamente il ciclo:

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

  });
</script>

Consulta l' API jQuery per i dettagli.


0

Questo non è il più efficiente, poiché continui a scorrere tutti gli elementi, ma ho pensato che valesse la pena considerare il molto semplice:

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

continueè una parola chiave, il tuo codice è un errore di sintassi.
Bergi,

3
Dato che stai usando ES6 comunque, dovresti semplicemente passare a un for ofloop e break;da quello come al solito.
Bergi,

riparato e vero - ma per lo più usava es6 per brevità
martyman il

0

puoi seguire il codice qui sotto che funziona per me:

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

Perché il -1? non è più brutto che catturare un'eccezione, che è un hack IMHO più grande.
Byron Whitlock,

0

Preferisco usare 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 infunziona in modo molto simile forEache puoi aggiungere la funzione return to exit all'interno. Prestazioni anche migliori.


0

Se devi interrompere in base al valore degli elementi che sono già nell'array come nel tuo caso (ovvero se la condizione di interruzione non dipende dalla variabile di runtime che può cambiare dopo che l'array ha assegnato i suoi valori di elemento) puoi anche usare la combinazione di slice () e indexOf () come segue.

Se devi interrompere quando ogni volta che raggiunge 'Apple' puoi usare

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

Come indicato in W3Schools.com, il metodo slice () restituisce gli elementi selezionati in un array, come un nuovo oggetto array. L'array originale non verrà modificato.

Guardalo in JSFiddle

Spero che aiuti qualcuno.


0

È possibile creare una variante di forEachche consente break, continue, return, e persino async/ await: (ad esempio scritto a macchina)

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

prova con "trova":

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

Sì, è possibile continuare e uscire da un ciclo forEach.

Per continuare, puoi usare return, il loop continuerà ma la funzione corrente terminerà.

Per uscire dal loop, è possibile impostare il terzo parametro su 0 length, impostare su array vuoto. Il loop non continuerà, la funzione corrente lo fa, quindi puoi usare "return" per finire, come esci in un normale per loop ...

Questo:

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

stamperà questo:

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

-2

Prima, il mio codice è sotto

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

                    return false;
                }
            });

Ho cambiato in basso, è stato risolto.

 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;
                }
            }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.