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

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

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

40 ответов

Решение

Там нет встроенной способности 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 (который остановится на return false), а также forEach все методы пятого издания ECMAScript, которые необходимо добавить к Array.prototype в браузерах, где они отсутствуют.

Теперь в 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;
}

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

[1,2,3].every(function(el) {
    return !(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;
  };
}

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

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

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

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

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

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

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

Из вашего примера кода это выглядит так Array.prototype.find это то, что вы ищете: Array.prototype.find () и Array.prototype.findIndex()

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

Подумайте об использовании jquery "s each метод, поскольку он позволяет возвращать false внутри функции обратного вызова:

$.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');
// => []

Если вы хотите использовать предложение Дина Эдварда и выбросить ошибку 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, только если она уже существует. Если он еще не существует, он не изменит его.

Краткий ответ: используйте 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() для этой части массива.

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

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

Как упоминалось ранее, вы не можете сломать .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!

связи

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

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

Использование:

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

Может потребоваться некоторая настройка, особенно для поддержки итерации свойств объекта.

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

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 внутри функции обратного вызова приведет к разрыву. Дайте мне знать, если это не сработает.

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

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

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

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

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

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

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

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

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

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

Я использую для этой цели 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
}
//

Я знаю, что это не правильно. Это не разорвать петлю. Это джугад

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

Почему бы вам не попробовать заключить функцию в обещание?

Единственная причина, по которой я поднимаю этот вопрос, заключается в том, что я использую функцию в API, которая действует аналогично forEach. Я не хочу, чтобы он продолжал повторяться после нахождения значения, и мне нужно что-то вернуть, поэтому я просто собираюсь разрешить Promise и сделать это таким образом.

      traverseTree(doc): Promise<any> {
  return new Promise<any>((resolve, reject) => {
    this.gridOptions.api.forEachNode((node, index) => {
    //the above function is the one I want to short circuit.
      if(node.data.id === doc.id) {
        return resolve(node);
      }
    });
  });
}

Тогда все, что вам нужно сделать, это сделать что-нибудь с результатом вроде

      this.traverseTree(doc).then((result) => {
   this.doSomething(result);
});

Мой пример выше находится в машинописном тексте, просто игнорируйте типы. Мы надеемся, что логика поможет вам «вырваться» из цикла.

Если вы хотите сохранить свой 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>

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

Использовать array.prototype.every Функция, которая предоставит вам утилиту для разрыва цикла. Смотрите пример здесь документации Javascript в сети разработчиков Mozilla

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

Правильный ответ зависит от того, почему вы хотите это сделать.

Если то, что вы хотите, это: "как чистый и лаконичный способ вырваться из forEach() насколько это возможно, учитывая, что break не разрешено ", тогда вы можете переопределить Array.forEach(), чтобы позволить вам сделать это:

[1,2,3,4,5].forEach((x,i,stop) => { // x and i are the standard 2 args
  if (x > 3) {
    stop() // You could call it 'end', 'brk' or whatever...
  }
  console.log(x)
})

Вот переопределение, обратите внимание, что forEach() обычно передает два параметра: текущий итерированный объект и индекс. Мы просто добавляем третий:

Array.prototype.forEach = function(fn) {
  var StopIteration = new Error("StopIteration");
  var len = this.length;
  function stop() {
    throw StopIteration;
  }
  for (i=0;i<len;i++) {
    try {
      fn(this[i], i, stop)
    } 
    catch(e) {
      if(e == StopIteration) {
        return
      }
      throw e;
    }
  }
}

Вы можете использовать любое имя, которое вам нравится (кроме break или любое другое зарезервированное ключевое слово, не повезло!)

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

Согласитесь с @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 для деталей.

Если вам нужно разбить на основе значения элементов, которые уже есть в вашем массиве, как в вашем случае (т.е. если условие разрыва не зависит от переменной времени выполнения, которая может измениться после присвоения массиву значений его элементов), вы также можете использовать комбинацию slice() и 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

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

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

      function breakAtThree() {
    [1, 2, 3].forEach(function (el) {
        el < 3 ? console.log(el) : null;
    });
}
breakAtThree()

Если ты действительно любишь forEach Я думаю, что это нормально, чтобы подражать continue сюда;

arr.forEach(x=> {
  if(x>4) return;
  // some heavy code down below
});

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

arr.forEach(x=> {
  if (x>4) {arr.length=0; return;}
  // some heavy code might be here
});

Вырваться из встроенногоArray.prototype.mapфункция esp в React

Ключевым моментом здесь является использование оператораreturnсломать

      let isBroken = false;

colours.map(item => {
    if (isBroken) {
        return;
    }
    if (item.startsWith("y")) {
        console.log("The yessiest colour!");
        isBroken = true;
        return;
    }
});

Дополнительная информация здесь: https://www.codegrepper.com/code-examples/javascript/break+out+of+map+javascript.

Другие вопросы по тегам