我从服务器抓取数据并将它们推送到一个可观察的数组中。
我将观察者推入可观察的阵列。
当我将数据推送到observable时,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
}
}
});
答案 0 :(得分:5)
你,先生,有一个典型的 async-brain-melt 案例。这是初学者常见的sympton,但从不担心恢复率接近100%。 :)
我打赌你的经验是使用同步语言,也就是说,如果一行写在另一行之后,之前写的行总是在之前执行。
普通的JavaScript函数是同步的。例如:
console.log(1);
console.log(2);
正如所料,这会打印1
,然后打印2
。
但是,异步代码不一定按声明的顺序执行。使用 setTimeout 函数考虑此示例,该函数调度函数以供稍后执行:
setTimeout(function(){ console.log(1); }, 1000);
console.log(2);
现在,输出将是2
和1
,因为1
仅在setTimeout
调用后运行了1000毫秒。
所以,我想你已经开始明白这是如何适用于你的问题的。
您对cats_url
和units_by_food_url
的来电是异步。因此,以下代码不会等待让它们完成。因此,当您访问self.mealFoods()[0].food.units()
时,success
函数尚未获取数据!
您需要做的是正确协调您的异步调用。有很多方法可以实现这一目标。首先,我将教你最简单的策略,只使用函数:
所以,它看起来像这样:
$.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
});
现在,你可能已经意识到这种模式是多么令人厌烦。这就是为什么还有其他方法可以更好地解决这个问题。其中之一是一种名为&#34; Promises&#34;的模式。您可以在以下链接中了解有关它们的更多信息:
https://www.promisejs.org/
http://blog.gadr.me/promises-are-not-optional/
您会很高兴知道jQuery.ajax()会返回一个Promise!所以,现在你可以尝试使用Promises来解决这个问题。您将获得更清晰的代码。
希望你成功!
答案 1 :(得分:2)
这是因为你在循环中进行异步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);