在Parse.com的Cloud代码中,异步代码为for循环中的变量提供了错误的值

时间:2014-08-13 09:41:41

标签: javascript asynchronous parse-platform

我正在尝试在parse.com上保存不同的食物名称而不重复。但是,当我运行代码时,数据库一遍又一遍地包含相同的2或3种食物,而不是200个左右的独特名称。

enter image description here

以下是我的功能。我尝试在两个不同的点记录食物的名称,我得到不同的值。第一点给出正确的食物名称,但第二点只显示亚麻籽松饼或覆盆子派。我认为这个问题与异步运行的代码有关,但我不知道如何解决这个问题。

Parse.Cloud.define("recordFavorite", function(request, response) {

      var foodList = request.params.foodList; //string array of food names
      var Food = Parse.Object.extend("Food");
      var query = new Parse.Query(Food);

      for (i = 0; i < foodList.length; i++ ) {
              var name = foodList[i];
              console.log("before name is " + name);
              var query = new Parse.Query(Food);
              query.exists("name", name);

              query.find({
              success: function(results) {
                if(results.length == 0){
                  var food = new Food();
                  food.set("name", name);
                  food.save(null, {
                    success: function(food) {
                      console.log("saved with name " + name);
                    },
                    error: function(food, error) {
                    }
                  });
                } else {
                  //don't create new food

                }
              },
              error: function(error) {
              }
            });

      }

});

修改

我可以通过将其修改为下面粘贴的代码来取得一些进展。现在它保存所有对象,包括重复对象。我注意到了这些行

var query = new Parse.Query(Food);
query.exists("name", name);

返回所有食物的数组,但不会过滤掉包含“name”的对象。 (很明显,这可能仍然出现在原始代码中,但我没有注意到。)

Parse.Cloud.define("recordFavorite", function(request, response) {

  var foodList = request.params.foodList; //string array of food names
  var foodListCorrected = new Array();
  var Food = Parse.Object.extend("Food");

  // Wrap your logic in a function
  function process_food(i) {
      // Are we done?
      if (i == foodList.length) {
          Parse.Object.saveAll(foodListCorrected, {
              success: function(foodListCorrected) {

              },
              error: function(foodListCorrected) {

              }
          });
          return;
      }

      var name = foodList[i];
      var query = new Parse.Query(Food);
      query.exists("name", name);

      query.find({
      success: function(results) {
        console.log(results.length);
        if(results.length == 0){
          //console.log("before " + foodListCorrected.length);
          var food = new Food();
          food.set("name", name);
          foodListCorrected.push(food);
        //  console.log(foodListCorrected.length);
        } else {
          //don't create new food
        }
        process_food(i+1)

      },
      error: function(error) {
        console.log("error");
      }
    });

    }

      // Go! Call the function with the first food.
      process_food(0);


    });

2 个答案:

答案 0 :(得分:2)

我认为你对异步逻辑的问题是正确的。问题是外部循环尽可能快地完成,为您的食物查询查询启动各种较慢的异步调用。外环并不等待,因为它们知道变量提升&#39;当你访问&#39; name&#39;在你的成功功能中,它的价值将是&#39; name&#39;的最新价值。在外循环中。因此,当调用success函数时,name的值已经移动到与第一次启动exists / save查询序列时不同的食物。

这是一个非常简单的例子:

说你的foodList看起来像[&#39; Muffin&#39;],[&#39;芝士蛋糕&#39;]。当您第一次进入循环时,您的名字=&#39; Muffin&#39;。您为name =&#39; Muffin&#39;而这现在是异步发生的。同时,外环快乐地移动并设置名称=&#39;芝士蛋糕&#39;并触发另一个存在的查询。与此同时。您的第一个存在的查询完成,您现在准备保存第一个食物。但是,由于提升,你的成功函数中名称的价值现在是&#39; Cheesecake&#39;。因此它节省了&Cheesecake&#39;什么时候应该保存松饼&#39;然后第二组异步查询完成,这个也保存了&#39; Cheesecake&#39;。所以你得到两种食物,代表你们两种独特的食物,但两者都被称为“芝士蛋糕”!

这是关于变量提升的经典文章,值得一读:

http://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html

解决这个问题的一种方法是,只有当所有异步调用当前食物完成后才触发下一个食物的处理。你可以这样做:

Parse.Cloud.define("recordFavorite", function(request, response) {

  var foodList = request.params.foodList; //string array of food names
  var Food = Parse.Object.extend("Food");
  var query = new Parse.Query(Food);

  // Wrap your logic in a function
  function process_food(i) {
      // Are we done?
      if (i == foodList.length) return;

      var name = foodList[i];
      console.log("before name is " + name);
      var query = new Parse.Query(Food);
      query.exists("name", name);

      query.find({
      success: function(results) {
        if(results.length == 0){
          var food = new Food();
          food.set("name", name);
          food.save(null, {
            success: function(food) {
              console.log("saved with name " + name);
              // Move onto the next food, only after all the async operations
              // have completed.
              process_food(i+1)
            },
            error: function(food, error) {
            }
          });
        } else {
          //don't create new food

        }
      },
      error: function(error) {
      }
    });
  }

  // Go! Call the function with the first food.
  process_food(0);
});

(注意,我没有测试过这段代码,因此可能存在语法错误。)

我之前没有遇到Parse ......我看到了你的问题,开始阅读它,并认为它看起来非常有趣!我将记住它为我的下一个PHP API项目。我认为你可以尝试做一些更聪明的事情。例如,您的方法需要每个食物有2个异步调用,一个用于查看是否存在,还有一个用于保存。对于200种食物,这是400次异步呼叫。但是,Parse API看起来非常有用,我认为它将提供工具来帮助您减少这种情况。您可以尝试以下几行:

您已经拥有要保存的名称字符串数组:

var foodList = request.params.foodList; //string array of food names

说它看起来像[&#34; Cupcakes&#34;,&#34; Muffins&#34;,&#34; Cake&#34;]。

现在构建一个Parse查询,获取服务器上已有的所有食品名称。 (我不知道该怎么做!)。但你应该回到一个阵列,让我们说[&#34; Cupcakes&#34;,&#34;芝士蛋糕&#34;]。

现在你在JavaScript中删除重复项。 StackOverflow上有一些很好的问题可以帮助解决这个问题!结果将是&#34; Cupcake&#34;是重复的,所以我们留下了数组[&#34; Muffins&#34;,&#34; Cake&#34;]

现在在Parse看起来你可以批处理一些操作:

https://parse.com/docs/rest#objects-batch

所以你的目标是通过一次API调用来保存这个[&#34; Muffins&#34;,&#34; Cake&#34;]数组。

这种方法可以很好地适应食物的数量,所以即使有200种食物,你应该能够在一次查询中完成,每50种食物更新一次(我认为50是批量限制,来自Parse docs),所以最多需要5个API调用。

答案 1 :(得分:0)

我相信这(https://www.parse.com/docs/js_guide#promises-series)是您正在寻找的解决方案。你需要利用承诺来强制同步。