Angular 中数组的异步更新
Async update of array in Angular
我有一个渲染推文的视图。在推文中,有 user_mentions 数组。从这个数组中,我想获得一个用户,向 API 发出另一个请求,获取用户的头像,然后呈现它。
视图看起来像:
<md-item ng-repeat="item in content">
<div class="md-tile-left" ng-if="item.show">
<img ng-src="{{item.thumb}}" class="face" alt="{{item.name}}">
</div>
<div class="md-tile-content">
{{item.text}}
</div>
</md-item>
content 变量将包含所有推文。最初,content[i].show 对所有 i 都是假的。控制器代码如下所示:
for(var i = 0; i < data.length; i++) {
if(data[i].user_mentions[0]) {
UserAvatar.get(data[i].user_mentions[0].screen_name).then(function(data){
$scope.content[i].thumb = data.avatar;
$scope.content[i].show = true;
});
}
}
现在,我面临的问题是调用UserAvatar服务后,变量i会继续递增。因此,在到达回调时,我将具有 data.length 的值(因此,将抛出未定义的错误)。我通过将 i 传递给 API 然后将其取回找到了解决方法:
UserAvatar.get(data[i].user_mentions[0].screen_name, i).then(function(data){
$scope.content[i].thumb = data.avatar;
$scope.content[i].show = true;
});
我意识到这只是让它工作的一种 hack,但是有没有更聪明的方法呢?
问题是您尝试在 for 循环内使用闭包,但它并没有像您发现的那样起作用。您的 hack 是修复它的一种选择。另一种方法是使用 forEach()
/map()
或 IIFE ((function(i) { ... })(i);
).
将循环包装在一个函数中
在您的示例中,我将执行以下操作:
var promises = data.map(function(item, index) {
if (item.user_mentions[0]) {
return UserAvatar.get(item.user_mentions[0].screen_name)
.then(function(itemData) {
$scope.content[index].thumb = itemData.avatar;
$scope.content[index].show = true;
});
} else {
return $q();
}
});
$q.all(promises).then(function() {
// All complete
});
I realise that this is just a hack to make it work, but is there a
more clever way of doing it?
正如@MukeshAgarwal 在他的评论中所说,这并不是真正的 hack,因为 UserAvatar.get
是一个异步过程。
因此,您必须在异步调用的时刻捕获 i
的值。
你用的方法还不错,但还有一个方法是使用立即调用的函数:
这个不起作用 :
for (var i = 0; i < 10; i++) {
// do something async
setTimeout(function() {
console.log(i); // <- This will log 10 each time (the value of i at execution)
}, 1000)
}
但是这有效:
for (var i = 0; i < 10; i++) {
(function() {
var j = i; // <- The value of i is captured in j. j is in a closure
// do something async
setTimeout(function() {
console.log(j); // <- This will log 0, then 1, then 2 ... then 9
}, 1000)
})()
}
无论何时调用一个函数,你都必须记住,当它被执行时,由外部函数创建的范围内的所有变量(不仅是 $scope
,还有像 i 这样的外部变量)对该函数都是可见的,我想你已经意识到这一点,重要的是要意识到你实际上并不需要 i
在 .then
的处理程序中你需要获取索引 i
处的内容但是你如何摆脱这种依赖?我提出了两种可能的解决方案:
1)
function getAvatar(name, contentToUpdate) {
return UserAvatar.get(name)
.then(function (data) {
contentToUpdate.thumb = data.avatar;
contentToUpdate.show = true;
});
}
for(var i = 0; i < data.length; i++) {
if(data[i].user_mentions[0]) {
getAvatar(data[i].user_mentions[0].screen_name, $scope.content[i]);
}
}
当调用 .then
函数的处理程序时,i
依赖项不再存在
2)
for(var i = 0; i < data.length; i++) {
if(data[i].user_mentions[0]) {
UserAvatar.get(data[i].user_mentions[0].screen_name).then((function(content) {
return function (data) {
content.thumb = data.avatar;
content.show = true;
}
})($scope.content[i]));
}
}
在这个解决方案中,一个 IIFE returns 一个将作为处理程序的函数,但 IIFE 实际上会捕获正在分析的内容的值,因此当内部函数返回时(即 onFulfilled
.then
) 的处理程序被调用没有 i
依赖性
奖励: 一条对我处理函数有很大帮助的规则如下:
A function will be executed in the place it was defined
不记得在哪里看过了,好像是Nicholas Zakas的Professional JavaScript for Web Developers,强烈推荐:)
我有一个渲染推文的视图。在推文中,有 user_mentions 数组。从这个数组中,我想获得一个用户,向 API 发出另一个请求,获取用户的头像,然后呈现它。 视图看起来像:
<md-item ng-repeat="item in content">
<div class="md-tile-left" ng-if="item.show">
<img ng-src="{{item.thumb}}" class="face" alt="{{item.name}}">
</div>
<div class="md-tile-content">
{{item.text}}
</div>
</md-item>
content 变量将包含所有推文。最初,content[i].show 对所有 i 都是假的。控制器代码如下所示:
for(var i = 0; i < data.length; i++) {
if(data[i].user_mentions[0]) {
UserAvatar.get(data[i].user_mentions[0].screen_name).then(function(data){
$scope.content[i].thumb = data.avatar;
$scope.content[i].show = true;
});
}
}
现在,我面临的问题是调用UserAvatar服务后,变量i会继续递增。因此,在到达回调时,我将具有 data.length 的值(因此,将抛出未定义的错误)。我通过将 i 传递给 API 然后将其取回找到了解决方法:
UserAvatar.get(data[i].user_mentions[0].screen_name, i).then(function(data){
$scope.content[i].thumb = data.avatar;
$scope.content[i].show = true;
});
我意识到这只是让它工作的一种 hack,但是有没有更聪明的方法呢?
问题是您尝试在 for 循环内使用闭包,但它并没有像您发现的那样起作用。您的 hack 是修复它的一种选择。另一种方法是使用 forEach()
/map()
或 IIFE ((function(i) { ... })(i);
).
在您的示例中,我将执行以下操作:
var promises = data.map(function(item, index) {
if (item.user_mentions[0]) {
return UserAvatar.get(item.user_mentions[0].screen_name)
.then(function(itemData) {
$scope.content[index].thumb = itemData.avatar;
$scope.content[index].show = true;
});
} else {
return $q();
}
});
$q.all(promises).then(function() {
// All complete
});
I realise that this is just a hack to make it work, but is there a more clever way of doing it?
正如@MukeshAgarwal 在他的评论中所说,这并不是真正的 hack,因为 UserAvatar.get
是一个异步过程。
因此,您必须在异步调用的时刻捕获 i
的值。
你用的方法还不错,但还有一个方法是使用立即调用的函数:
这个不起作用 :
for (var i = 0; i < 10; i++) {
// do something async
setTimeout(function() {
console.log(i); // <- This will log 10 each time (the value of i at execution)
}, 1000)
}
但是这有效:
for (var i = 0; i < 10; i++) {
(function() {
var j = i; // <- The value of i is captured in j. j is in a closure
// do something async
setTimeout(function() {
console.log(j); // <- This will log 0, then 1, then 2 ... then 9
}, 1000)
})()
}
无论何时调用一个函数,你都必须记住,当它被执行时,由外部函数创建的范围内的所有变量(不仅是 $scope
,还有像 i 这样的外部变量)对该函数都是可见的,我想你已经意识到这一点,重要的是要意识到你实际上并不需要 i
在 .then
的处理程序中你需要获取索引 i
处的内容但是你如何摆脱这种依赖?我提出了两种可能的解决方案:
1)
function getAvatar(name, contentToUpdate) {
return UserAvatar.get(name)
.then(function (data) {
contentToUpdate.thumb = data.avatar;
contentToUpdate.show = true;
});
}
for(var i = 0; i < data.length; i++) {
if(data[i].user_mentions[0]) {
getAvatar(data[i].user_mentions[0].screen_name, $scope.content[i]);
}
}
当调用 .then
函数的处理程序时,i
依赖项不再存在
2)
for(var i = 0; i < data.length; i++) {
if(data[i].user_mentions[0]) {
UserAvatar.get(data[i].user_mentions[0].screen_name).then((function(content) {
return function (data) {
content.thumb = data.avatar;
content.show = true;
}
})($scope.content[i]));
}
}
在这个解决方案中,一个 IIFE returns 一个将作为处理程序的函数,但 IIFE 实际上会捕获正在分析的内容的值,因此当内部函数返回时(即 onFulfilled
.then
) 的处理程序被调用没有 i
依赖性
奖励: 一条对我处理函数有很大帮助的规则如下:
A function will be executed in the place it was defined
不记得在哪里看过了,好像是Nicholas Zakas的Professional JavaScript for Web Developers,强烈推荐:)