【问题标题】:Observable values disappearing after being pushed into observableArrayObservable 值在被推入 observableArray 后消失
【发布时间】:2015-03-09 17:43:37
【问题描述】:

我正在从服务器获取数据并将它们推送到可观察的数组中。

我将 observables 推送到一个 observable 数组中。

当我将数据推送到可观察对象中时,可观察对象包含数据。

但是,一旦我将 observables 推送到 observable Array 中,一些 observables 就会丢失数据。

      self.mealFoods([]);

      $.ajax({
        url: "/mealsurl/1",
        async: false,
        dataType: 'json',
        success: function(datad) {

          for(var lia = 0; lia < datad.length; lia++){
            var cats_url = "/catsurl/" + datad[lia].category_id;

            var units_by_food_url = "/unitsurl/" + datad[lia].ndb_no;

            var foodThing = new NewFood();

            foodThing.foodId(parseInt(datad[lia].id)); //works

            foodThing.category(parseInt(datad[lia].category_id)); //works

            $.ajax({
              url: cats_url,
              dataType: 'json',
              success: function(dat) {
                foodThing.category_foods(dat); //works
              }
            });

            foodThing.food(datad[lia].ndb_no); //works

            $.ajax({
              url: units_by_food_url,
              dataType: 'json',
              success: function(dat) {
                foodThing.food.units(dat); //works
              }
            });

            foodThing.unit(parseInt(datad[lia].seq)); //works

            foodThing.number_of_unit(datad[lia].this_much); //works



            self.mealFoods.push(foodThing); 

            // At this point when looking inside the mealFoods array: self.mealFoods()[0].food(), self.mealFoods()[0].unit(), self.mealFoods()[0].food.units(), self.mealFoods()[0].category_Foods() ALL ARE EMPTY

          }              
        }
      });

【问题讨论】:

  • 是否可以提供一个 jsfiddle 来演示该问题?您还应该提供 NewFood 的代码
  • 嘿韦恩。有点奇怪。它只发生在服务器端数据上。当我伪造它时-它起作用了。
  • 我现在可以看到问题了。这是因为 Ajax 调用是异步的。我回家后会发布答案
  • 其实你的问题根本不在于Knockout,而在于ajax的异步执行!

标签: ajax asynchronous knockout.js closures


【解决方案1】:

这是因为您在循环中执行异步 ajax 调用。因为每当进行 ajax 调用时,循环就会继续,这意味着当响应返回时,分配给 foodThing 的对象现在不再是在 ajax 调用之前设置的对象。因为 for 循环非常快,很可能只有循环中创建的最后一个对象被更新。

如果你看一下这个简单的循环,它也有同样的问题:

for (var i = 0; i < 10; i++){
    var a = new NewFood(i);
    $.ajax({
        url: "/catsurl/1",
        dataType: 'json',
        success: function(dat) {
            console.debug(a.id);
        }
    });
}

当 ajax 调用返回时,a 已经改变,最终发生的事情只有 9 被写出 10 次:http://jsfiddle.net/r6rwbtb9/

为了解决这个问题,我们将使用一个闭包,该闭包本质上是将 ajax 调用包装在一个函数中,在该函数中我们自己包含我们想要使用的项目:

for (var i = 0; i < 10; i++){
    var a = new NewFood(i);
    (function (a) {
        $.ajax({
            url: "/catsurl/1",
            dataType: 'json',
            success: function(dat) {
                console.debug(a.id);
            }
        });
    })(a);
}

然后你可以看到数字0-9被输出到控制台:http://jsfiddle.net/r6rwbtb9/1/。值得注意的是,您无法确保每个请求都一定会以相同的顺序返回,这也很有趣。这就是为什么有时数字可能会以不同的顺序返回到 0-9,因为有些请求比其他请求更快。

回到你的代码。为了确保为每个回调更新正确的项目,您需要为每个 ajax 调用使用闭包。 foodThing.food.units(dat) 也有一个问题,它必须是 foodThing.food().units(dat),因为 foodThing.food() 是一个 observable。

所以要封装闭包,我们需要将两个 ajax 调用更改为:

(function(category_foods){
    $.ajax({
      url: cats_url,
      dataType: 'json',
      success: function(dat) {
        category_foods(dat); 
      }
    });
})(foodThing.category_foods);

(function(units){
    $.ajax({
      url: units_by_food_url,
      dataType: 'json',
      success: function(dat) {
        units(dat); 
      }
    });
})(foodThing.food().units);

【讨论】:

  • 嘿,韦恩。很好的解决方案。但是,我觉得您错过了解释为什么会起作用的原因。看来OP是初学者。如果您能阅读我的答案并告诉我是否应该添加一些内容,我将非常高兴。谢谢!
  • @gadr90 是的。我试图解释。我真的很难解释这些事情。我会努力改进的
  • @gadr90 我更新了它。现在肯定更长了,但希望它是有意义的。
【解决方案2】:

先生,您遇到了一个典型的 async-brain-melt 案例。这是初学者的常见症状,但不要担心恢复率接近100%。 :)

我敢打赌你的经验是使用 同步 语言,也就是说,如果一行写在另一行之后,那么之前写的行总是先执行。

普通的 JavaScript 函数是同步的。例如:

console.log(1);
console.log(2);

正如预期的那样,这会打印1,然后是2

但是,异步代码不一定按照声明的顺序执行。考虑使用 setTimeout 函数的示例,该函数会安排一个函数供以后执行:

setTimeout(function(){ console.log(1); }, 1000);
console.log(2);

现在,输出将是21,因为1setTimeout 调用之后只运行了1000 毫秒。

所以,我想您已经开始了解这如何适用于您的问题。

您对cats_urlunits_by_food_url 的调用是异步。因此,以下代码不会等待它们完成。所以,当你访问self.mealFoods()[0].food.units()时,success函数还没有抓取到数据!

您需要做的是适当地协调异步调用。有很多方法可以实现这一目标。首先,我会教你最简单的策略,只使用函数:

  • 从服务器获取列表
  • 获得列表后,遍历每餐并启动两个 ajax 调用(到此为止,您已经做对了所有事情)
  • 现在神奇了:当您获得 either ajax 调用的结果时,您可以调用“itemComplete”函数。此函数将同步两个调用 - 只有在两个调用完成后才会继续。
  • 最后,每次完成任何项目时调用“listComplete”函数。此功能还必须在继续之前检查所有项目是否完整。

所以,它看起来像这样:

$.ajax({
  url: "/meals/1",
  dataType: 'json',
  success: function(list) {

    var observableArray = ko.observableArray([]); // this will hold your list
    var length = list.length;
    var tries = 0;
    var listComplete = function () {
      tries++;
      if (tries == length) {
        // Hooray! 
        // All your items are complete.
        console.log(observableArray());
      }
    };

    list.forEach(function(item){
      var propertyOneUrl = item.propertyOneUrl;
      var propertyTwoUrl = item.propertyTwoUrl; 

      var propertyOneComplete = false;
      var propertyTwoComplete = false;

      var food = new Food(item.id);

      var itemComplete = function () {
        if (propertyOneComplete && propertyTwoComplete) {
          // This item is complete.
          observableArray.push(food);

          // Let's warn list complete so it can count us in.
          listComplete();
        }
      };

      // Start your ajax calls
      $.ajax({
        url: propertyOneUrl,
        dataType: 'json',
        success: function (propertyOne) {
          food.propertyOne(propertyOne);
          // Declare that your first property is ready
          propertyOneComplete = true;
          // We can't know which property finishes first, so we must call this in both
          itemComplete();
        }
      });

      $.ajax({
        url: propertyTwoUrl,
        dataType: 'json',
        success: function (propertyTwo) {
          food.propertyTwo(propertyTwo);
          // Declare that your second property is ready
          propertyTwoComplete = true;
          // We can't know which property finishes first, so we must call this in both
          itemComplete();
        }
      });
    }); //for each
  } // success   
});

现在,您可能已经意识到这种模式是多么令人厌烦。这就是为什么有其他方法可以更好地解决这个问题。其中之一是称为“承诺”的模式。您可以通过以下链接了解更多信息:

https://www.promisejs.org/
http://blog.gadr.me/promises-are-not-optional/

你会很高兴知道 jQuery.ajax() 返回一个 Promise!所以,现在你可以尝试使用 Promises 解决这个问题。你最终会得到一个更简洁的代码。

希望你能成功!

【讨论】:

  • 这是一个绝妙的答案。我从这个答案中学到的关于 JavaScript 的知识比我一生中做过的任何事情都多。谢谢。
猜你喜欢
  • 1970-01-01
  • 2014-01-06
  • 1970-01-01
  • 1970-01-01
  • 2016-03-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多