可观察值在被推入 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);
现在,输出将是 2
和 1
,因为 1
在 setTimeout
调用后仅 运行 1000 毫秒。
所以,我想您已经开始理解这如何适用于您的问题了。
您对 cats_url
和 units_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 来解决这个问题。您最终会得到更简洁的代码。
希望你成功!
我正在从服务器抓取数据并将它们推送到一个可观察数组中。
我正在将 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);
现在,输出将是 2
和 1
,因为 1
在 setTimeout
调用后仅 运行 1000 毫秒。
所以,我想您已经开始理解这如何适用于您的问题了。
您对 cats_url
和 units_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 来解决这个问题。您最终会得到更简洁的代码。
希望你成功!