【问题标题】:Nodejs asynchronous with while loopNodejs与while循环异步
【发布时间】:2016-01-28 18:16:50
【问题描述】:

所以我有这个代码:

Rating.find({user: b}, function(err,rating) {
            var covariance=0;
            var standardU=0;
            var standardV=0;

            while (rating.length>0){
                console.log("the avarage rating u is:" + avarageRatingU)
                console.log("the avarage rating v is:" + avarageRatingV)
                currentMovie = rating.pop();
                var u=currentMovie.value-avarageRatingU;
                standardU = standardU + Math.pow(u,2);
                var v=0;
                Rating.find({movieid:currentMovie.movieid, user:a}, function(err,ratings) {
                    if (err) throw err;
                    if(ratings.length>0){
                        v=ratings.pop().value-avarageRatingV;
                        standardV=standardV+Math.pow(v,2);
                        covariance =covariance+u*v;
                        console.log(covariance);

                    }
                })
            }
            console.log(covariance)
            callback(null,covariance);
            //sim = covariance/(Math.sqrt(standardU)*Math.sqrt(standardV));
        })

问题是当我打印协方差时,它会打印 0,因为打印发生在计算之前。我考虑过使用 async.series,但是,我不能在 while 循环中使用回调函数。

任何提示将不胜感激。

泰:)

【问题讨论】:

  • 你有search吗? “我考虑过使用 async.series,但是,我不能在 while 循环中使用回调函数。” 这是什么意思?如果您使用 async 函数来代替,您将不再有 while 循环。

标签: javascript node.js asynchronous synchronous


【解决方案1】:

您使用的是哪个 orm?,我认为更好的方法是按“movieid”对结果进行分组,然后计算每个组的协方差,而无需对每个“评分”进行查询结果。

Rating.find({user: b}, function(err, rating){
    var covariance=0;
    var standardU=0;
    var standardV=0;
    var ratingsByMovieId = groupByMovieId(rating);
    while(rating.length > 0){
        console.log("the avarage rating u is:" + avarageRatingU)
        console.log("the avarage rating v is:" + avarageRatingV)
        currentMovie = rating.pop();
        var u=currentMovie.value-avarageRatingU;
        standardU = standardU + Math.pow(u,2);
        var v=0;
        ratingsByMovieId[currentMovie.movieid].forEach(function(ratings){
            // this code isn't asynchronous :)
            // because the code isn't asynchronous you don't need a callback.
        });
    }
});

function groupByMovieId(ratings) {
    var groups = {};
    ratings.forEach(function(rating){
        var movieId = rating.movieid;
        groups[movieId] = groups[movieId] || [];
        groups[movieId].push(rating);
    });
    return groups;
}

我没有测试过这段代码 jeje :)

【讨论】:

  • orm = 对象关系映射器,类似于 mongoose 或 sequelize。我问,因为也许您的 orm 有某种方法可以按“movieid”对您的结果进行分组。
【解决方案2】:

你不能像这样在循环中调用你的回调吗?不确定这是否对您有帮助。

while (rating.length>0){
  console.log("the avarage rating u is:" + avarageRatingU)
  console.log("the avarage rating v is:" + avarageRatingV)
  currentMovie = rating.pop();
  var u=currentMovie.value-avarageRatingU;
  standardU = standardU + Math.pow(u,2);
  var v=0;
  Rating.find({movieid:currentMovie.movieid, user:a}, function(err,ratings) {
    if (err) throw err;
    if(ratings.length>0){
      v=ratings.pop().value-avarageRatingV;
      standardV=standardV+Math.pow(v,2);
      covariance =covariance+u*v;
      console.log(covariance);
    }
    if(rating.length == 0){
      console.log(covariance);
      callback(null, covariance);
    }
  })
}

【讨论】:

  • 我实际上认为这会起作用,但它没有打印出任何东西..但是谢谢你的提示!
  • 不客气。可能是评分不大于 0 吧?尝试将 if(!isBiggerThanZero){etc..} 放在最后一个 if 语句之后: if(ratings.length>0){etc... } 而不是 inside。也许这会有所帮助
  • 是的,你是对的。但是,现在解决了这个问题。在我的 async.series 的最后一个函数中:function(err,result){ console.log(avarageRatingU);控制台.log(avarageRatingV); console.log(协方差); });它实际上为它们中的每一个打印出正确的值。但是,打印出来后,我得到一个“错误:回调已被调用。”
  • 这太奇怪了。所以看起来 if(isBiggerThanZero == false) 语句中的函数存储了答案(协方差),直到 isbiggerthanzero 为假,然后打印出所有答案,因此,回调发生了多次。
  • 你看到我编辑了我的答案吗?像这样我觉得应该和以前基本一样,只是写法不同而已。对不起,如果我不能帮助你更多,我还在学习 Javascript。是的,这听起来很奇怪。
【解决方案3】:

在这种情况下,使用 Promise 或异步库代替传统的 while 循环是一种方法。由于您已经熟悉异步,因此请提前使用 async.map (https://github.com/caolan/async#map) 获取所需的所有数据。

Rating.find({user: b}, function(err,rating) {
        var covariance=0;
        var standardU=0;
        var standardV=0;

        aync.map(rating, function(currentMovie, cb) {
            Rating.find({ movieid:currentMovie.movieid, user:a }, function(err,ratings) {
                cb(err, ratings);
            });
        }, function(err, results) {
            if (!err) {
                // compute covariance with all movie data
            }
        });
    })

【讨论】:

  • 我建议使用 async.eachSeries,但我更喜欢这个答案。大量使用函数式编程,因为您的结果数组可以很容易地简化为协方差,并且不需要状态/全局变量。
【解决方案4】:

这个怎么样:

Rating.find({user: b}, function(err,rating) {
        var covariance=0;
        var standardU=0;
        var standardV=0;

        var c = rating.length; // loop counter
        var r = 0; // received counter
        function aux_callback (covariance) {
            r++;
            if (r==c)
                callback(null,covariance);
        }

        while (rating.length>0){
            console.log("the avarage rating u is:" + avarageRatingU)
            console.log("the avarage rating v is:" + avarageRatingV)
            currentMovie = rating.pop();
            var u=currentMovie.value-avarageRatingU;
            standardU = standardU + Math.pow(u,2);
            var v=0;
            Rating.find({movieid:currentMovie.movieid, user:a}, function(err,ratings) {
                if (err) throw err;
                if(ratings.length>0){
                    v=ratings.pop().value-avarageRatingV;
                    standardV=standardV+Math.pow(v,2);
                    covariance =covariance+u*v;
                    console.log(covariance);
                    aux_callback(covariance);
                }
            })
        }
        //sim = covariance/(Math.sqrt(standardU)*Math.sqrt(standardV));
    })

您需要从循环内的 lambda 函数触发回调,以确保在运行该代码之前它不会发生。鉴于您似乎需要完成所有循环,我创建了两个计数器,c 用于跟踪 lambda 必须运行多少次,r 用于计算您调用 aux_callback 的次数。此代码应等到您计算完所有协方差后再调用callback

【讨论】:

  • 谢谢,好主意!但是,c 不会总是大于 r 吗?意味着永远不会调用回调
  • 你是对的,'r' 索引有一点问题,我修复了它并将'c' 初始化为循环的长度,因此没有风险执行 lambda 函数的速度一样快作为循环:)
  • 还有一个问题,ratings.length可能不大于0,意思是aux_callback,不会被调用。所以我把它放在我最后一个 if 语句之外。但是,我仍然得到 0 作为协方差
【解决方案5】:

这是在循环中调用异步函数的标准问题。

让我们先看一个更简单的例子。假设我想要一个在 for 循环中打印 i 值的函数,并且我希望它异步运行:

for (i = 0; i < 5; i++) { 
    setTimeout(function() { console.log(i); }, 1);
}

现在这将实际上只打印出 5、5 次。这是因为异步函数setTimeout在循环结束后返回,而i在执行时的值是5。如果我们想让它打印出0-4,那么我们需要做如下:

for (i = 0; i < 5; i++) { 
    (function(i) {
        setTimeout(function() { console.log(i); }, 1);
    })(i);
}

注意匿名函数如何将i 作为参数。这会创建一个闭包,在调用时“保存”i 的值。

现在回到你的问题:

covariance = covariance + u * v;

这发生在您的console.log(covariance) 语句之前,这意味着您的打印语句实际上在计算之后发生的。这意味着此时covariance 确实等于 0。

您的 covariance 变量被初始化为 0。uv 也是如此。 0 + 0 * 0 = 0。好的,太好了:所以如果 uv 没有正确设置,至少数学检查出来了。

让我们回到你的循环:

while (rating.length>0){
    currentMovie = rating.pop();
    var u=currentMovie.value-avarageRatingU;
    var v=0;
    Rating.find({movieid:currentMovie.movieid, user:a}, function(err,ratings) {
        if (err) throw err;
        if(ratings.length>0){
            v=ratings.pop().value-avarageRatingV;
            standardV=standardV+Math.pow(v,2);
            covariance =covariance+u*v;
...

在这里,我们可以看到您的异步Rating.find 回调正在提取u 的最后一个已知值,即它在while 循环的end 处的值。在Rating.find 回调之外定义v 确实没有任何理由。

如果您想要每个循环的 u 的值,请尝试将其包装在匿名自执行函数中以“保存”该值,如下所示:

Rating.find({user: b}, function(err,rating) {
    var covariance = 0;
    var standardU = 0;
    var standardV = 0;
    var u = 0;

    while (rating.length > 0){
        console.log("the avarage rating u is:" + avarageRatingU)
        console.log("the avarage rating v is:" + avarageRatingV)
        currentMovie = rating.pop();
        u = currentMovie.value - avarageRatingU;
        standardU = standardU + Math.pow(u, 2);
        (function(u) {
            Rating.find({ movieid: currentMovie.movieid, user: a }, function(err, ratings) {
                if (err) throw err;
                if (ratings.length > 0) {
                    var v = ratings.pop().value - avarageRatingV;
                    standardV = standardV + Math.pow(v,2);
                    covariance = covariance + u * v;
                    console.log(covariance);

                }
            });
        })(u);
    }
    console.log(covariance)
    callback(null,covariance);
    //sim = covariance/(Math.sqrt(standardU)*Math.sqrt(standardV));
});

我还将u 的声明移到了循环之外(在循环中声明它们是不好的做法,因为您每次都在重新实例化变量)。我将 v 的声明 inside 移到了 Rating.find 回调中,因为它甚至没有在外面使用。

【讨论】:

  • 嗨! ty 输入。我接受了关于把你带到外面,把v放在里面的建议:)但是,循环内的“console.log(covariance)”是正确的,但它不是我想要的,它是第二个console.log(covariance)正在打印出 0,我想修复它:)
  • 我明白你在说什么。在循环中,设置一些变量等于您的Rating.find 调用,然后使用when 语句在最后一次Rating.find 调用发生时执行您的回调。看个例子纯JSwhen function here.
【解决方案6】:

假设 rating(或 rating)是一个数组,您需要对其进行迭代并对每个项目(例如 Rating.find)调用异步函数,async.eachSeries 可能最有意义。

async.eachSeries(ratings, function iterator(rating, callback) {
  // invoke async function here for each rating item
  // and perform calculations
}, function done() {
  // all async functions have completed, print your result here
});

更多文档在这里:https://github.com/caolan/async

【讨论】:

    【解决方案7】:

    尝试使用 Promise!现在 Node.js 4.x 已经发布,所有的 JavaScript ES6(和 Promises)都随之而来。您可以查看 Mozilla 基金会文档以获取 Promise here

    如果您以前从未使用过 Promise,它真正做的就是允许您在值可用之前返回一个值。之后,promise 可以变为 resolvedrejected 并具有实际价值。

    在这种情况下,您需要将内部 Rating.find() 调用封装在 new Promise( function(resolve, reject) { ... } ) 中,并将 then() 子句附加到您的承诺上,该子句会打印出协方差的值。当您从内部 Rating.find() 函数中检索真正的协方差时,只需使用 resolve 参数(这是一个采用单个值的函数)并传递协方差,以便将其传递到您的函数中Promise 的 then() 子句,将打印协方差。

    如果有任何困惑,请给我评论!

    【讨论】:

      【解决方案8】:

      这是我今晚的第二个 promise 解决方案 :)。所以,让我试试:

          //supposing you are using node js
          var Q = require('q'); //https://github.com/dscape/nano
          Rating.find({user: b}, function(err,rating) {
                      var covariance=0;
                      var standardU=0;
                      var standardV=0;
                      var promises = [];
                      while (rating.length>0){
                          console.log("the avarage rating u is:" + avarageRatingU)
                          console.log("the avarage rating v is:" + avarageRatingV)
                          currentMovie = rating.pop();
                          var u=currentMovie.value-avarageRatingU;
                          standardU = standardU + Math.pow(u,2);
                          var v=0;
                          var def = Q.defer();
                          promises.push(def);
                          Rating.find({movieid:currentMovie.movieid, user:a}, function(err,ratings) {
                              if (err) {
                                  def.reject();
                                  throw err;
                              }
                              if(ratings.length>0){
                                  v=ratings.pop().value-avarageRatingV;
                                  standardV=standardV+Math.pow(v,2);
                                  covariance =covariance+u*v;
                                  def.resolve();
                                  //console.log(covariance);
                              }
      
                          });
      
                      }
                      Q.allSettled(promises, function() {
                          console.log(covariance);    
                      });          
                      callback(null,covariance);
                      //sim = covariance/(Math.sqrt(standardU)*Math.sqrt(standardV));
                  });
      

      【讨论】:

      • 那行不通。在您调用Q.allSettled 的那一刻,promises 是一个空数组。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-03-01
      • 2019-02-15
      • 2018-11-05
      相关资源
      最近更新 更多