Короткое замыкание Array.forEach как вызов прерывания


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

Как я могу сделать это, используя новый forEachметод в JavaScript? Я пытался return;, return false;и break. breakпадает и returnничего не делает, кроме продолжения итерации.


6
Стоит отметить, что хотя returnитерация действительно продолжается, она пропускает любой код, следующий за ним в блоке. Возьмите этот код, например: [1,2,3].forEach(function(el) { if(el === 2) { console.log(`Match on 2!`); return; } console.log(el); });.The console.log(el);будет пропущен, когда 2 соответствует.
Шейн

5
TL; DR: Я сэкономлю большинство из вас много времени. Я использую много JS в последнее время. Ответ (из 28 ...), который вы, вероятно, ищете, следующий: stackoverflow.com/a/32101207/1599699
Эндрю

Ответы:


2143

Там нет встроенной способности breakв forEach. Чтобы прервать выполнение, вам нужно было бы выдать какое-то исключение. например.

var BreakException = {};

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

Исключения в JavaScript не очень красивые. Традиционный forцикл может быть более уместным, если вам действительно нужно breakвнутри него.

использование Array#some

Вместо этого используйте Array#some:

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

Это работает, потому что someвозвращает, trueкак только любой из обратных вызовов, выполненных в порядке массива, возвращается true, замыкая выполнение остальных.

some, его обратный every(который остановится на a return false), и forEachвсе методы пятого издания ECMAScript, которые необходимо добавить в Array.prototypeбраузеры, где они отсутствуют.


111
Это не более читабельно и не более производительно, чем обычное использование цикла for. Ответ должен быть «не используйте forEach в этом случае» -1
BT

37
Я думаю, что «некоторые» в порядке, почему бы не использовать раннюю оптимизацию выхода
chrismarx

28
Спасибо за внимание someи every, это должно быть в ТОПе в ответе. Не могу понять, почему люди думают, что это менее читабельно. Это просто потрясающе!
Карл Адлер

9
Использование Array#someдействительно приятно. Во-первых, он совместим с большинством браузеров, включая ie9 и firefox 1.5, также работает очень хорошо. Мой пример использования будет заключаться в том, чтобы найти индекс в массиве диапазонов [a, b], где число находится между нижней границей и парой верхней границы, проверить и вернуть true, когда найден. for..ofбудет следующим лучшим решением, хотя только для новых браузеров.
Sojimaxi

96
Обработка исключений НИКОГДА не должна использоваться в качестве потока управления. СРОК.
откровенная

479

Теперь в ECMAScript2015 (он же ES6) есть еще лучший способ сделать это с использованием нового цикла for . Например, этот код не печатает элементы массива после числа 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;
  }
}

Из документов:

И для ... в, и для ... операторов перебирают что-то. Основное различие между ними заключается в том, что они повторяют. Оператор for ... in перебирает перечисляемые свойства объекта в исходном порядке вставки. Оператор for ... of перебирает данные, которые итерируемый объект определяет для перебора.

Нужен индекс в итерации? Вы можете использовать Array.entries():

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

4
@superhero Вы можете получить индекс элемента в цикле for ..., вы просто должны использовать entries. for (const [index, element] of someArray.entries ()) {// ...}
blackxored

не рекомендуется не использовать для ... в массивах?
Schehata

4
@emostafa Вы правильно о для в циклах не рекомендуется для массивов, но это на самом деле подход использует для из цикла.
Canac

Это «для», и это действительно чистое решение ... но это также особенность ES6, поэтому просто знайте, что это будет работать, только если ваша среда настроена для ES6.
Чад

Я часто использую это решение и использую его и для объектов. С объектами вы можете сделать, Object.entries(myObject)а затем использовать его точно так же, как вы используете for..inдля массива. Обратите внимание, что массивы JS в основном являются объектами под капотом: blog.niftysnippets.org/2011/01/myth-of-arrays.html
Эндрю

204

Вы можете использовать любой метод:

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

ES6

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

для поддержки старых браузеров используйте:

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

подробнее здесь .


10
Хороший и чистый в ES6 сейчас -[1,2,3].every( el => el !== 1 )
метам

1
@Valdemar, а every гарантирует ли, что звонки выполняются последовательно?
Pacerier

4
@Pacerier, вы можете увидеть алгоритм в спецификации ES6, где индекс kначинается с 0 и увеличивается на 1: http://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.every.
XP1

@ XP1, все ли разработчики должны делать это так?
Pacerier

1
@Pacerier, да, большинство популярных реализаций работают правильно. Если вас интересуют встроенные реализации, обычно это Opera или webkit. Каждый метод вызывает callbackfn один раз для каждого элемента, присутствующего в массиве, в порядке возрастания , пока не найдет тот, где callbackfn возвращает false. Также посмотрите на шаг 7. Пусть k будет 0. и 8.e Увеличьте k на 1.
Valdemar_Rudolfovich

78

Цитирование из документации MDNArray.prototype.forEach() :

Там нет никакого способа , чтобы остановить или сломать в forEach()петлю другого , чем бросать исключение. Если вам нужно такое поведение, .forEach()метод является неправильным инструментом , вместо этого используйте простой цикл. Если вы тестируете элементы массива на предмет предиката и вам нужно логическое возвращаемое значение, вы можете использовать его every()или some()вместо него.

Для вашего кода (в вопросе), как предложено @bobince, используйте Array.prototype.some()вместо этого. Это очень хорошо подходит для вашего использования.

Array.prototype.some()выполняет функцию обратного вызова один раз для каждого элемента, присутствующего в массиве, пока не найдет элемент, где обратный вызов возвращает истинное значение (значение, которое становится истинным при преобразовании в a Boolean). Если такой элемент найден, some()немедленно возвращается true. В противном случае some()возвращает false. обратный вызов вызывается только для индексов массива, которым присвоены значения; он не вызывается для индексов, которые были удалены или которым никогда не присваивались значения.


1
Это правильный ответ. 'some' делает именно то, что делает foreach / break. Это повторяется до итерации n = true.
Энтони Бут

74

К сожалению, в этом случае будет намного лучше, если вы не используете forEach. Вместо этого используйте обычный forцикл, и теперь он будет работать точно так, как вы ожидаете.

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

27
Меня шокирует тот факт, что наибольшее число голосов является худшей из возможных реализаций по сравнению с более высокой производительностью, меньшим количеством кода и лучшей читаемостью этого правильного ответа. Брось исключение ... правда? Является ли традиционный цикл for просто недостаточно kewl?
gdbj

2
@gdbj Я согласен с вашим утверждением и использовал этот метод, но что действительно шокирует меня, так это то, что без этих хаков невозможно выйти из forEach, теперь это плохой дизайн.
СкоттN

28

Рассмотрим использование jquery«s eachметод, так как он позволяет вернуть ложную функцию обратного вызова внутри:

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

Библиотеки Lodash также предоставляют takeWhileметод, который может быть связан с map / lower / fold и т.д .:

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
Хорошая причина использовать jQuery. forEach в родном javascript по-прежнему отсутствует.
Алекс Гранде

3
@AlexGrande jQuery forEach и JavaScript forEach не совместимы.
Бьорн

10
JavaScript используется во многих местах, что JQuery не вариант.
Дж.Б. Уилкинсон


18

Если вы хотите использовать предложение Дина Эдварда и выбросить ошибку StopIteration, чтобы выйти из цикла без необходимости перехвата ошибки, вы можете использовать следующую функцию ( изначально отсюда ):

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

Приведенный выше код даст вам возможность запускать код, подобный следующему, без необходимости создавать собственные предложения 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);
});

Важно помнить, что функция Array.prototype.forEach будет обновляться только в том случае, если она уже существует. Если он еще не существует, он не изменит его.


11

Краткий ответ: используйте for...breakдля этого или измените свой код, чтобы избежать взлома forEach. Не используйте .some()или .every()подражать for...break. Перепишите свой код, чтобы избежать for...breakзацикливания, или используйте for...break. Каждый раз, когда вы используете эти методы в качестве for...breakальтернативы, Бог убивает котенка.

Длинный ответ:

.some()и .every()оба возвращают booleanзначение, .some()возвращает, trueесли есть какой-либо элемент, для которого возвращается переданная функция true, каждый возвращает, falseесли есть какой-либо элемент, для которого возвращается переданная функцияfalse . Вот что означают эти функции. Использование функций для того, что они не имеют в виду, гораздо хуже, чем использование таблиц для разметки вместо CSS, потому что это расстраивает всех, кто читает ваш код.

Кроме того, единственный возможный способ использовать эти методы в качестве for...breakальтернативы - создать побочные эффекты (изменить некоторые переменные вне .some()функции обратного вызова), и это мало чем отличается отfor...break .

Таким образом, использование .some()или .every()как for...breakальтернатива цикла не свободна от побочных эффектов, это не намного чище, чемfor...break , это расстраивает, так что это не лучше.

Вы всегда можете переписать свой код так, чтобы в нем не было необходимости for...break. Вы можете фильтровать массив с помощью .filter(), вы можете разделить массив с помощью .slice()и так далее, затем использовать .forEach()или .map()для этой части массива.


использование .filter на самом деле является подходящим решением для многих случаев взлома.
TKoL

Как насчет производительности? Не повлияет ли фильтр на производительность при частом использовании?
tfrascaroli

Да, прототип массива фильтров может стать тяжелым. Мне это нравится, но это может повлиять на производительность, если его чрезмерно использовать.
Чад,

@tfrascaroli используйте for...breakцикл, если вам нужна производительность. forпетля является наиболее производительным инструментом итерации , чем .forEach(), .any(), .map(), и .filter()т.д.
Max

6

Это просто то, что я придумал, чтобы решить проблему ... Я почти уверен, что это решает проблему, которая была у оригинального аскера:

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

И тогда вы бы назвали это с помощью:

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

Возврат false внутри функции обратного вызова вызовет разрыв. Дайте мне знать, если это не сработает.


1
=== falseэто может быть лучше, чем то, == falseчто вам не нужно явно возвращать true (или истинное значение), чтобы продолжить цикл, иначе какой-то путь управления не вернет значение и цикл неожиданно прервется.
Джейк

6

Еще одна концепция, которую я придумал:

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


Синтаксис похож на [NSArray enumerateObjectsUsingBlock], спасибо!
Chrstph SLN

@Drenai подпись аналогична родной Array.prototype.forEach(). forи breakсуществовал задолго до того, как был задан этот вопрос; ОП искал такое поведение, используя более функциональный forEach.
c24w

@Drenai теперь удалил свой комментарий (но оставил отрицательный голос), в котором упоминалось, что подпись этого решения трудно запомнить и не нужна, когда вы можете решить проблему с помощью for...inи break.
c24w


5

Нашел это решение на другом сайте. Вы можете обернуть forEach в сценарий 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; }

Подробнее здесь: http://dean.edwards.name/weblog/2006/07/enum/


2
Не используйте исключения в качестве операторов потока управления. Используйте его для обработки неожиданных результатов.
Макс

4

Если вам не нужен доступ к вашему массиву после итерации, вы можете выручить, установив длину массива равной 0. Если вам все еще нужно это после итерации, вы можете клонировать его с помощью слайса.

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

Или с клоном:

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

Это гораздо лучшее решение, чем случайные ошибки в вашем коде.


молодец :) , но если есть какие - то действия после назначения array.lengthна 0они будут применяться в текущей итерации, так что, вероятно , это иногда лучше использовать returnпосле такого присвоившей
zhibirc

4

Это цикл for, но он поддерживает ссылку на объект в цикле, как и forEach (), но вы можете выйти из него.

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

4

Как упоминалось ранее, вы не можете сломаться .forEach().

Вот немного более современный способ создания foreach с помощью итераторов ES6. Позволяет получить прямой доступ к index/ valueпри итерации.

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

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

Вывод:

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

связи


3

Еще один подход

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

Это правильное использование метода фильтра? Я предполагаю, что обратный вызов возвращает логическое значение, что независимо от него, обратный вызов называется элементами массива до последнего.
MortezaE

2

Я использую nullhack для этой цели, он пытается получить доступ к свойству null, что является ошибкой:

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
Почему не просто throw BREAK?
Берги

1

Если вы хотите сохранить свой forEachсинтаксис, это способ сохранить его эффективность (хотя и не так хорошо, как обычный цикл for). Немедленно проверьте переменную, которая знает, хотите ли вы выйти из цикла.

В этом примере используется анонимная функция для создания области действия, вокруг forEachкоторой вам нужно хранить готовую информацию.

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

Мои два цента.




0

Согласитесь с @bobince, проголосовал.

Кроме того, к вашему сведению:

В Prototype.js есть что-то для этого:

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

$break будет перехватываться и обрабатываться внутренним Prototype.js, прерывая цикл «каждый», но не генерируя внешних ошибок.

См. Prototype.JS API для деталей.

У jQuery также есть способ, просто верните false в обработчик, чтобы разорвать цикл раньше:

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

  });
</script>

Смотрите jQuery API для подробностей.


0

Это не самый эффективный способ, так как вы по-прежнему переключаете все элементы, но я подумал, что стоит подумать об очень простом:

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

continueявляется ключевым словом, ваш код является синтаксической ошибкой.
Берги

3
Учитывая, что вы все равно используете ES6, вам следует просто переключиться на for ofцикл и break;, как обычно, делать это .
Берги

исправлено, и правда - но в основном для краткости использовал es6
martyman

0

Вы можете следовать приведенному ниже коду, который работает для меня:

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

Почему -1? это не страшнее, чем поймать исключение, это больше, взломать IMHO.
Байрон Уитлок

0

Я предпочитаю использовать 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работает так же, как forEach, и вы можете добавить функцию возврата к выходу внутри. Лучшая производительность тоже.


0

Если вам нужно разбить на основе значений элементов, которые уже есть в вашем массиве, как в вашем случае (т.е. если условие разрыва не зависит от переменной времени выполнения, которая может измениться после присвоения массиву значений его элементов), вы также можете использовать комбинацию из среза () и IndexOf () следующим образом .

Если вам нужно сломать, когда forEach достигает «Apple», вы можете использовать

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

Как сказано в W3Schools.com метод slice () возвращает выбранные элементы в массиве как новый объект массива. Исходный массив не будет изменен.

Смотрите это в JSFiddle

Надеюсь, это кому-нибудь поможет.


0

Вы можете создать вариант из forEachчто позволяет break, continue, returnи даже async/ await(например , написанные в машинописи)

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

Применение:

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

попробуйте с "найти":

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

Да, можно продолжить и выйти из цикла forEach.

Чтобы продолжить, вы можете использовать return, цикл продолжится, но текущая функция завершится.

Чтобы выйти из цикла, вы можете установить третий параметр равным 0 длины, установить пустой массив. Цикл не будет продолжаться, текущая функция делает, так что вы можете использовать «возврат» для завершения, как выход в нормальном для цикла цикла ...

Эта:

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

напечатает это:

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

-2

Раньше мой код был ниже

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

                    return false;
                }
            });

Я изменил ниже, это было исправлено.

 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;
                }
            }
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.