как остановить Javascript forEach?

Я играю с Node.js и Mongoose - пытаюсь найти конкретный комментарий в глубоких комментариях, вложенных с рекурсивной функцией и forEach внутри. Есть ли способ остановить Node.js forEach? Насколько я понимаю, каждая forEach итерация - это функция, и я не могу просто сделать break, только return, но это не остановит forEach.

function recurs(comment) {
    comment.comments.forEach(function(elem) {

        recurs(elem);

        //if(...) break;

    });
}

person kulebyashik    schedule 07.06.2011    source источник


Ответы (13)


Вы не можете оторваться от forEach. Однако я могу придумать три способа подделать это.

1. Уродливый путь: передайте второй аргумент forEach в использовать в качестве контекста и сохранить там логическое значение, а затем использовать if. Это выглядит ужасно.

2. Спорный путь: заключите все в try-catch блок и создайте исключение, если хотите прервать работу. Это выглядит довольно плохо и может повлиять на производительность, но может быть инкапсулирован.

3. The Fun Way: используйте every().

['a', 'b', 'c'].every(function(element, index) {
  // Do your thing, then:
  if (you_want_to_break) return false
  else return true
})

Вы можете использовать some() вместо этого, если вы предпочитаете return true сломаться.

person slezica    schedule 07.06.2011
comment
+1, хотя мне кажется более естественным использовать some() и return true, когда вы хотите сломать. - person Giacomo; 07.06.2011
comment
Или, что более элегантно, поместите return !you_want_to_break внутри цикла вместо блока if..else. Сохраняет две строчки. :-) - person sffc; 28.11.2013
comment
every поддерживается везде, кроме IE7 и 8 (пришлось поискать, поэтому решил поделиться) - person jbobbins; 19.09.2014
comment
Там должен быть номер 4. Прекрасный способ: используйте some() - person Preexo; 21.10.2014
comment
Это должен быть так называемый «вводящий в заблуждение способ». Название предполагает, что это результат, а не побочные эффекты, которые вам небезразличны. - person joozek; 27.11.2014
comment
@jbobbins Еще одна причина отказаться от поддержки IE8;) - person Elia Iliashenko; 05.07.2015
comment
MDN также предлагает полифилл: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/. - person Konstantin Grushetsky; 13.07.2015
comment
Также обратите внимание на то, что, поскольку _1 _ / _ 2_ возвращает true, если какой-либо элемент делает это, вы можете сойти с ума и вложить это злоупотребление сколь угодно глубоко .. - person OJFord; 27.08.2015
comment
К сожалению, использование every () или some () не решит случаев, когда вы хотели бы разбить конкретное значение и вернуть его. ECMAScript2015, новый для ... из (developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/) может помочь с этой частью, но недостатком является то, что это решение может вызвать еще больше проблем со старыми браузерами. Если вы хотите полностью изменить маршрут и использовать другой, более общий подход, такое решение (github.com/nbouvrette / forEach) может помочь вам и потенциально решить даже другие проблемы. - person Nicolas Bouvrette; 13.06.2016
comment
Что касается №2, вы говорите, что у него может быть плохая работа - почему? - person noahnu; 11.03.2017
comment
Может кто-нибудь объяснить №1? Как это сделать? Просто интересно. - person through.a.haze; 16.03.2017
comment
ВАЖНАЯ ЗАМЕТКА! every прерывается только со значениями == false, тогда как some прерывается только со значениями == true. Убедитесь, что вы понимаете эту концепцию, прежде чем использовать этот трюк! - person Jacksonkr; 21.07.2017
comment
Третий способ только завершает цикл every (). Следующие инструкции все еще выполняются. - person DarkMoon; 07.08.2017
comment
@slezica, не могли бы вы рассказать об уродливом способе? - person Pyromonk; 10.09.2017
comment
Второй аргумент .forEach(callback, context) - это объект context, к которому можно получить доступ как this из обратного вызова. Вы можете поместить туда объект со свойством, которое сообщает вам, следует ли обрабатывать элементы или нет. Это по-прежнему подразумевает линейный переход по массиву, если только вы не throw. - person slezica; 10.09.2017
comment
Сторонние библиотеки позволяют использовать ту же логику every: если вы вернете false, это нарушит each Loop. В ES5, однако, every - это лучший вариант. - person Bernardo Dal Corno; 08.03.2018
comment
Как насчет карт? - person Mathijs Segers; 12.04.2018
comment
Или вы можете запустить forEach в реплике вашего массива, а когда вы хотите сломать, просто «склейте» основной массив, но мы не можем использовать этот способ везде. почему бы нам не использовать «for» или «while», если мы хотим прервать цикл. - person Kanad Chourasia; 28.08.2019
comment
Я столкнулся с той же дилеммой. Я просто преобразовал forEach в старый добрый цикл for и использовал break и покончить с этим. Самое приятное то, что мне не нужно беспокоиться о том, работает ли он во всех популярных браузерах. - person dotcoder; 06.10.2020

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

Вместо этого вы должны использовать обычный цикл for:

function recurs(comment) {
    for (var i = 0; i < comment.comments.length; ++i) {
        var subComment = comment.comments[i];
        recurs(subComment);
        if (...) {
            break;
        }
    }
}

(или, если вы хотите быть немного умнее и comment.comments[i] всегда является объектом :)

function recurs(comment) {
    for (var i = 0, subComment; subComment = comment.comments[i]; ++i) {
        recurs(subComment);
        if (...) {
            break;
        }
    }
}
person Domenic    schedule 07.06.2011
comment
Это возможно, добавив внутрь функции forEach, как указано в принятом ответе. - person Estus Flask; 23.09.2016
comment
конечно, но использование исключений для управления потоком программы - плохая практика - person Igor Donin; 16.02.2018

В некоторых случаях Array.some, вероятно, будет соответствовать требованиям. .

person igor    schedule 04.10.2012
comment
Это должен быть канонический ответ, поскольку он фактически прекратит обработку, как только найдет правильный элемент. Хотя forEach и every (насколько я понимаю) можно взломать, чтобы вернуть истину для первого найденного элемента, он все равно будет проходить через весь массив. Во-вторых, Javascript не выполняет оптимизацию хвоста, и, таким образом, все рекурсивные функции по своей природе хрупки до тех пор, пока не появится ES6. - person Indolering; 20.11.2013
comment
как поддержка браузера? - person imal hasaranga perera; 01.04.2017
comment
@imalhasarangaperera Здесь вы можете найти текущую поддержку браузера. developer.mozilla.org/ en-US / docs / Web / JavaScript / Reference / - person Senthe; 21.04.2017

Как указывали другие, вы не можете отменить цикл forEach, но вот мое решение:

ary.forEach(function loop(){
    if(loop.stop){ return; }

    if(condition){ loop.stop = true; }
});

Конечно, это на самом деле не разрывает цикл, а просто предотвращает выполнение кода для всех элементов, следующих за «break».

person Mark Kahn    schedule 07.06.2011
comment
Мне нравится этот. Я бы просто скомбинировал последнюю строку с loop.stop = condition. Это не должно иметь никакого значения, потому что, когда он установлен в true, он больше не будет запускаться. - person pimvdb; 07.06.2011
comment
Умное использование именованных функциональных выражений - person Raynos; 07.06.2011
comment
Я не думаю, что это решение - хорошая идея. представьте, что вы зацикливаете 10000 элементов, и ваше условие остановлено на втором элементе, тогда вы собираетесь делать ненужные итерации 9998 раз ни за что. Лучше всего использовать some или every. - person Ali; 07.03.2014
comment
@Ali zyklus сказал, что .... это еще одно правильное решение! Зависит от вашего случая .... Почему люди тратят время на критику вместо того, чтобы предлагать новые новые решения ...?!?!? - person Pedro Ferreira; 28.03.2016
comment
@PedroFerreira мой аргумент верен. Я никого не обижаю. Нас нужно критиковать за наш код, чтобы он стал лучше. Я бы предпочел прочитать другие реализации и помочь им улучшить их, чем изобретать велосипед. - person Ali; 29.03.2016
comment
@PedroFerreira Аргумент верен точно. Однако в некоторых случаях использования, когда я знаю, что количество элементов не будет очень большим, я буду чувствовать себя в безопасности при его использовании. Хорошо иметь это в качестве опции. :) - person Alacritas; 13.09.2018

forEach не ломается при возврате, есть уродливые решения для этой работы, но я предлагаю не использовать его, вместо этого попробуйте использовать Array.prototype.some или Array.prototype.every

var ar = [1,2,3,4,5];

ar.some(function(item,index){
  if(item == 3){
     return true;
  }
  console.log("item is :"+item+" index is : "+index);
});

person imal hasaranga perera    schedule 20.04.2017
comment
он работает, но он будет перебирать все члены массива - person Ankur Shah; 24.07.2018
comment
извините, произошла ошибка, вам нужно вернуть true - person imal hasaranga perera; 08.08.2018

Думаю, вы хотите использовать Array.prototype. find Find сломается, когда найдет ваше конкретное значение в массиве.

var inventory = [
  {name: 'apples', quantity: 2},
  {name: 'bananas', quantity: 0},
  {name: 'cherries', quantity: 5}
];

function findCherries(fruit) { 
  return fruit.name === 'cherries';
}

console.log(inventory.find(findCherries)); 
// { name: 'cherries', quantity: 5 }
person Shrihari Balasubramani    schedule 11.07.2017

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

Пример:

var _ = require('lodash');

_.forEach(comments, function (comment) {
    do_something_with(comment);

    if (...) {
        return false;     // Exits the loop.
    }
})
person exmaxx    schedule 21.10.2016

Array.forEach нельзя взломать, и использование try...catch или хакерских методов, таких как Array.every или Array.some, только усложнит понимание вашего кода. Есть только два решения этой проблемы:

1) используйте старый цикл for: это будет наиболее совместимое решение, но его может быть очень трудно прочитать при частом использовании в больших блоках кода:

var testArray = ['a', 'b', 'c'];
for (var key = 0; key < testArray.length; key++) {
    var value = testArray[key];
    console.log(key); // This is the key;
    console.log(value); // This is the value;
}

2) используйте новую версию ECMA6 (спецификация 2015 г.) в тех случаях, когда совместимость не является проблемой. Обратите внимание, что даже в 2016 году только несколько браузеров и IDE предлагают хорошую поддержку этой новой спецификации. Хотя это работает для итерируемых объектов (например, массивов), если вы хотите использовать это для не повторяемых объектов, вам нужно будет использовать метод Object.entries. По состоянию на 18 июня 2016 года этот метод почти не доступен, и даже для Chrome требуется специальный флаг: chrome://flags/#enable-javascript-harmony. Для массивов вам все это не понадобится, но совместимость остается проблемой:

var testArray = ['a', 'b', 'c'];
for (let [key, value] of testArray.entries()) {
    console.log(key); // This is the key;
    console.log(value); // This is the value;
}

3) Многие согласятся, что ни первый, ни второй вариант не подходят. Пока вариант 2 не станет новым стандартом, самые популярные библиотеки, такие как AngularJS и jQuery, предлагают свои собственные методы цикла, которые могут превосходить все, что доступно в JavaScript. Также для тех, кто еще не использует эти большие библиотеки и ищет облегченные варианты, такие решения, как this, могут будет использоваться и будет почти на уровне ECMA6, сохраняя при этом совместимость со старыми браузерами.

person Nicolas Bouvrette    schedule 18.06.2016

Код ниже прервет цикл foreach при выполнении условия, ниже приведен пример

    var array = [1,2,3,4,5];
    var newArray = array.slice(0,array.length);
    array.forEach(function(item,index){
        //your breaking condition goes here example checking for value 2
        if(item == 2){
            array.length = array.indexOf(item);
        }

    })
    array = newArray;
person D G ANNOJIRAO    schedule 13.06.2017
comment
не вызовет ли это проблемы с утечкой памяти? - person Jasti Sri Radhe Shyam; 24.07.2018
comment
определенно, это не приведет к утечке памяти, потому что мы устанавливаем исходный массив с такими же точными данными, как он есть, и это не будет висячим указателем, если вас все еще беспокоит переменная newArray, которая использовалась выше, после переназначения array = newArray вам можно установить newArray = null - person D G ANNOJIRAO; 24.07.2018

Вы можете выйти из цикла forEach, если перезапишете метод Array:

(function(){
    window.broken = false;

        Array.prototype.forEach = function(cb, thisArg) {
            var newCb = new Function("with({_break: function(){window.broken = true;}}){("+cb.replace(/break/g, "_break()")+"(arguments[0], arguments[1], arguments[2]));}");
            this.some(function(item, index, array){
                 newCb(item, index, array);
                 return window.broken;
            }, thisArg);
            window.broken = false;
        }

}())

пример:

[1,2,3].forEach("function(x){\
    if (x == 2) break;\
    console.log(x)\
}")

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

Счастливого взлома!

person ApplePear    schedule 16.05.2016
comment
Никогда не расширяйте прототип, вы можете сломать вещи, не зная, и это сломает вам разум! Такое плохое решение - person Lukas Liesis; 20.09.2016
comment
Две плохие практики в одном коде. - person MrHIDEn; 01.12.2020

Почему бы не использовать простой возврат?

function recurs(comment){
comment.comments.forEach(function(elem){
    recurs(elem);
    if(...) return;
});

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

person morterad    schedule 12.03.2014
comment
Вы не можете вернуться в петлю - person Anonymoose; 12.03.2014
comment
@Hazaart Хотя вы не можете сделать это в forEach, вы можете возвращаться в циклах, и функция завершится без дальнейших итераций цикла. (например, for (... in ...), while и т. д.) - person Sung Cho; 09.07.2015

jQuery предоставляет метод each(), а не forEach(). Вы можете вырваться из each, вернув false. forEach() является частью стандарта ECMA-262, и единственный способ выйти из этого, о котором я знаю, - это выбросить исключение.

function recurs(comment) {
  try {
    comment.comments.forEach(function(elem) {
      recurs(elem);
      if (...) throw "done";
    });
  } catch (e) { if (e != "done") throw e; }
}

Уродливо, но делает свою работу.

person Joe Taylor    schedule 07.06.2011
comment
-1 для использования jquery, когда OP специально сказал node.js - person Mark Kahn; 07.06.2011
comment
@cwolves Я думал, что где-то видел jQuery. Думаю, нет, но все равно, потому что jQuery и node.js можно использовать вместе друг с другом. - person Joe Taylor; 07.06.2011
comment
нет, вы не можете использовать jQuery в узле. Во всяком случае, есть явная ссылка на window, которая выдаст ошибку при загрузке в узел. Тогда у вас есть весь потраченный впустую код: весь движок Sizzle, вся корневая функция jQuery (буквально - в ней есть ссылка на document - она ​​ожидает DOM ) и т. д. Единственное, что вы можете использовать из jQuery в узле, - это несколько вспомогательных функций, и у них есть клоны в других библиотеках, таких как подчеркивание, которые намного лучше подходят для узла. - person Mark Kahn; 07.06.2011
comment
@cwolves Думаю, я начинаю ржаветь. Я думал о Rhino. - person Joe Taylor; 08.06.2011

person    schedule
comment
Хороший! Я бы использовал третий аргумент для обратного вызова и длину для него. - person igor; 27.04.2017
comment
дай этому мужчине печенье ! - person Ugur Kazdal; 09.03.2020