可观察值在被推入 observableArray 后消失

Observable values disappearing after being pushed into observableArray

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

我正在将 observable 推入一个 observable 数组。

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

然而,一旦我将可观察对象推入可观察数组,一些可观察对象就会丢失数据。

      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

          }              
        }
      });

这是因为您正在循环执行异步 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() 是一个可观察的。

因此,为了包裹闭包,我们需要将两个 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);

先生,您有一个典型的 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   
});

现在,您可能意识到这种模式有多么令人厌烦。这就是为什么有其他方法可以更好地解决这个问题。其中之一是称为 "Promises" 的模式。您可以在这些链接中了解更多关于它们的信息:

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

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

希望你成功!