【问题标题】:"continue" in cursor.forEach()cursor.forEach() 中的“继续”
【发布时间】:2013-08-29 11:05:00
【问题描述】:

我正在使用meteor.js 和MongoDB 构建一个应用程序,我有一个关于cursor.forEach() 的问题。 我想在每次 forEach 迭代开始时检查一些条件,然后如果我不需要对其进行操作则跳过该元素,这样我可以节省一些时间。

这是我的代码:

// Fetch all objects in SomeElements collection
var elementsCollection = SomeElements.find();
elementsCollection.forEach(function(element){
  if (element.shouldBeProcessed == false){
    // Here I would like to continue to the next element if this one 
    // doesn't have to be processed
  }else{
    // This part should be avoided if not neccessary
    doSomeLengthyOperation();
  }
});

我知道我可以使用 cursor.find().fetch() 将光标转换为数组,然后使用常规 for 循环迭代元素并正常使用 continue 和 break 但我很感兴趣是否有类似的东西可以使用在 forEach() 中。

【问题讨论】:

  • for(let element of data) { if(element.childData == "") { continue; } } 最佳解决方案

标签: javascript mongodb foreach meteor


【解决方案1】:

forEach() 的每次迭代都会调用您提供的函数。要在任何给定的迭代中停止进一步处理(并继续下一项),您只需在适当的点从函数中 return

elementsCollection.forEach(function(element){
  if (!element.shouldBeProcessed)
    return; // stop processing this iteration

  // This part will be avoided if not neccessary
  doSomeLengthyOperation();
});

【讨论】:

  • 你知道什么可能是“中断”然后如果继续只是“返回;”。
  • 我不使用 MongoDB,所以没有阅读它的文档,但 return false; 可能等同于 break;(就像 jQuery .each() 循环一样)。当然,实现 MongoDB 的.forEach() 的人可能还有其他想法……
  • @Drag0 您可以使用 .some() 作为 .forEach() 的替代,这使您可以返回 false 以中断循环。
  • @Andrew 您可以使用some,但请注意您正在滥用(或创造性地使用)旨在判断任何元素是否符合条件的功能。有点像当我看到人们使用map 并忽略结果时(他们应该使用forEach)。这是语义,当你并不真正关心时,人们将不得不看两次才能知道你为什么使用some结果
  • @Andrew 很棒的提示,但是 return true 会打破一些循环
【解决方案2】:

在我看来,最好的方法是使用filter method,因为在forEach 块中返回是没有意义的;以您的 sn-p 为例:

// Fetch all objects in SomeElements collection
var elementsCollection = SomeElements.find();
elementsCollection
.filter(function(element) {
  return element.shouldBeProcessed;
})
.forEach(function(element){
  doSomeLengthyOperation();
});

这将缩小您的 elementsCollection 并仅保留应处理的 filtred 元素。

【讨论】:

  • 这会将找到的元素迭代两次,一次在filter中,第二次在forEach中,如果它是一个大集合,它将非常低效
  • 你是对的,但我认为这没什么大不了的,因为它的时间复杂度是O(2n),可以认为是O(n)
  • 考虑到 SO 正在被其他人使用,而不仅仅是 OP,发布解决方案只是为了发布它,这样做弊大于利。上面的答案是在一次迭代中完成的,并且是 right 的方式。
  • 请注意,OP 的集合不是一个数组,它是一个 Mongo DB 游标对象,它似乎没有 .filter() 方法,所以你必须调用它的 .toArray() 方法在你可以.filter()之前
【解决方案3】:

这是使用for ofcontinue 代替forEach 的解决方案:


let elementsCollection = SomeElements.find();

for (let el of elementsCollection) {

    // continue will exit out of the current 
    // iteration and continue on to the next
    if (!el.shouldBeProcessed){
        continue;
    }

    doSomeLengthyOperation();

});

如果您需要在循环中使用在 forEach 中不起作用的异步函数,这可能会更有用。例如:


(async fuction(){

for (let el of elementsCollection) {

    if (!el.shouldBeProcessed){
        continue;
    }

    let res;

    try {
        res = await doSomeLengthyAsyncOperation();
    } catch (err) {
        return Promise.reject(err)
    }

});

})()

【讨论】:

    【解决方案4】:

    使用 JavaScript short-circuit 评估。如果el.shouldBeProcessed 返回真,doSomeLengthyOperation

    elementsCollection.forEach( el => 
      el.shouldBeProcessed && doSomeLengthyOperation()
    );
    

    【讨论】:

      【解决方案5】:

      简单的答案是将return 语句放入forEach 循环中,就像@nnnnnn 所说的那样,

      elementsCollection.forEach(function(element){
        if (!element.shouldBeProcessed)
          return; // stop processing this iteration
      
        // This part will be avoided if not neccessary
        doSomeLengthyOperation();
      });
      

      但如果你想深入回答这个问题,那就跟我来吧。

      假设您不知道forEach 循环的实现,那么请看一下forEach 循环的以下实现,这正是ECMA-262 第5 版中为forEach 循环指定的那个。

      来源 Array.prototype.forEach() - JavaScript | MDN

      if (!Array.prototype['forEach']) {
      
        Array.prototype.forEach = function(callback, thisArg) {
      
          if (this == null) { throw new TypeError('Array.prototype.forEach called on null or undefined'); }
      
          var T, k;
          // 1. Let O be the result of calling toObject() passing the
          // |this| value as the argument.
          var O = Object(this);
      
          // 2. Let lenValue be the result of calling the Get() internal
          // method of O with the argument "length".
          // 3. Let len be toUint32(lenValue).
          var len = O.length >>> 0;
      
          // 4. If isCallable(callback) is false, throw a TypeError exception.
          // See: https://es5.github.com/#x9.11
          if (typeof callback !== "function") { throw new TypeError(callback + ' is not a function'); }
      
          // 5. If thisArg was supplied, let T be thisArg; else let
          // T be undefined.
          if (arguments.length > 1) { T = thisArg; }
      
          // 6. Let k be 0
          k = 0;
      
          // 7. Repeat, while k < len
          while (k < len) {
      
            var kValue;
      
            // a. Let Pk be ToString(k).
            //    This is implicit for LHS operands of the in operator
            // b. Let kPresent be the result of calling the HasProperty
            //    internal method of O with argument Pk.
            //    This step can be combined with c
            // c. If kPresent is true, then
            if (k in O) {
      
              // i. Let kValue be the result of calling the Get internal
              // method of O with argument Pk.
              kValue = O[k];
      
              // ii. Call the Call internal method of callback with T as
              // the this value and argument list containing kValue, k, and O.
              callback.call(T, kValue, k, O);
            }
            // d. Increase k by 1.
            k++;
          }
          // 8. return undefined
        };
      }
      

      你真的不需要理解上面代码的每一行,因为我们感兴趣的是while循环,

      while (k < len) {
      
            var kValue;
      
            // a. Let Pk be ToString(k).
            //    This is implicit for LHS operands of the in operator
            // b. Let kPresent be the result of calling the HasProperty
            //    internal method of O with argument Pk.
            //    This step can be combined with c
            // c. If kPresent is true, then
            if (k in O) {
      
              // i. Let kValue be the result of calling the Get internal
              // method of O with argument Pk.
              kValue = O[k];
      
              // ii. Call the Call internal method of callback with T as
              // the this value and argument list containing kValue, k, and O.
              callback.call(T, kValue, k, O);
            }
            // d. Increase k by 1.
            k++;
          }
      

      如果你注意到了,那么这里有一个声明 callback.call(T, KValue, K, O),我们对这里给 call() 方法的参数不感兴趣,但我们真正感兴趣的是 callback 绑定,它是一个 function你给你的forEach javascript 循环。请参阅 call 方法仅调用它所调用的对象(javascript 函数),并使用 this 值和单独提供的参数。

      如果您不明白什么是调用,请查看Function.prototype.Call() - JavaScript | MDN

      如果在任何时候你的函数callback 在这种情况下返回,请考虑这一点,循环将照常更新。循环不关心callback 函数是否执行了给它的每一个步骤,如果控制返回到循环,则循环必须完成它的工作。每次循环更新时,callback 都会使用一组新值调用,如您所见,T, KValue, K, O 每次循环更新时都会发生变化,因此,如果您在任何时候从函数返回,即 callback,您就是只需将控制权交给被调用的循环即可.

      这就是您在 forEach 循环内跳过迭代的方式。

      【讨论】:

        【解决方案6】:

        如果您使用经典的for 循环并且不想使用continue,您可以在其中使用自执行函数并使用return 以模仿continue 的行为:

        for (let i = 0; i < 10; i++) {
            (() => {
                if (i > 5) return;
                console.log("no.", i)
            })();
        }
        
        console.log("exited for loop")
        

        输出:

        [LOG]: "no.",  0 
        [LOG]: "no.",  1 
        [LOG]: "no.",  2 
        [LOG]: "no.",  3 
        [LOG]: "no.",  4 
        [LOG]: "no.",  5 
        [LOG]: "exited for loop" 
        

        【讨论】:

          【解决方案7】:

          使用 continue 语句而不是 return 来跳过 JS 循环中的迭代。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2016-01-30
            • 2014-09-27
            • 2017-09-01
            • 2010-10-11
            • 1970-01-01
            • 2014-10-19
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多